2013-09-27 54 views
6

我使用的std ::結合提供回調而通過首先結合一些參數提取一些邏輯。即仿效的std ::結合用C

void start() { 

    int secret_id = 43534; 

    //Bind the secret_id to the callback function object 
    std::function<void(std::string)> cb = std::bind(&callback, secret_id, std::placeholders::_1); 

    do_action(cb); 

} 

void do_action(std::function<void(std::string)> cb) { 

    std::string result = "hello world"; 
    //Do some things... 

    //Call the callback 
    cb(result); 
} 

void callback(int secret_id, std::string result) { 

    //Callback can now do something with the result and secret_id 

} 

所以在上面的例子中,do_action並不需要了解secret_id等功能可以重複使用,而無需自己的secret_id。當do_action是某種異步操作時,這特別有用。

我的問題是,是否有綁定的參數值只使用C到函數指針的方法嗎?

如果不通過模擬的std ::綁定然後有另一種方式第一()回調(),而不中性do_action()複雜化來傳遞從數據?

回答

12

號C不會讓你直接做。

在C標準的方式來處理回調使用上下文指針:

void register_callback(void (*cback)(void *context, int data), 
         void *context); 

這意味着你會通過,將接受除回調應該處理的正常參數void *功能(在上面的情況是一個整數),你也會傳遞一個你想傳回的void *

這個void *通常指向一個struct,它將包含回調中需要的所有額外參數或數據,並且使用這種方法庫不依賴於這個上下文是什麼。如果回調不需要任何上下文,只需傳遞一個NULL指針作爲context,並在從庫中調用時忽略第一個參數。

的東西,是一種hackish的正式不安全,但它有時做的是,如果上下文是符合一個void *(例如整數)的大小,一個簡單的數據,如果你的環境是不會有它的問題您可以通過傳遞一個假的void *來欺騙庫,它只是一個整數,並且在從庫中調用時將其轉換回整數(這樣可以節省調用者分配上下文並管理其生存期)。

對我怎麼樣怎麼樣辦法來避免這種限制(便攜式C的土地仍然保留)的語言能想到一些黑客:

首先,我們分配了兩個論點回調和上下文數據

void (*cbf[6])(int, int); 
int ctx[6]; 

然後我們寫(或宏觀產生),我們想註冊,這將調用兩個參數版本的功能。

void call_with_0(int x) { cbf[0](ctx[0], x); } 
void call_with_1(int x) { cbf[1](ctx[1], x); } 
void call_with_2(int x) { cbf[2](ctx[2], x); } 
void call_with_3(int x) { cbf[3](ctx[3], x); } 
void call_with_4(int x) { cbf[4](ctx[4], x); } 
void call_with_5(int x) { cbf[5](ctx[5], x); } 

我們也將它們存儲在他們分配和釋放池:

int first_free_cback = 0; 
int next_free_cback[6] = {1, 2, 3, 4, 5, -1}; 

void (*cbacks[6])(int) = { call_with_0, 
          call_with_1, 
          call_with_2, 
          call_with_3, 
          call_with_4, 
          call_with_5 }; 

然後我們可以這樣做

void (*bind(void (*g)(int, int), int v0))(int) 
{ 
    if (first_free_cback == -1) return NULL; 
    int i = first_free_cback; 
    first_free_cback = next_free_cback[i]; 
    cbf[i] = g; ctx[i] = v0; 
    return cbacks[i]; 
} 

但綁定函數的第一個參數綁定還必須明確釋放

int deallocate_bound_cback(void (*f)(int)) 
{ 
    for (int i=0; i<6; i++) { 
     if (f == cbacks[i]) { 
      next_free_cback[i] = first_free_cback; 
      first_free_cback = i; 
      return 1; 
     } 
    } 
    return 0; 
} 
3

簡短的回答是否定的。

你唯一能做的就是聲明另一個內置了secret_id的函數。如果您使用的是C99或更新的版本,則可以將其設置爲內聯函數,以至少限制函數調用開銷,但是更新的編譯器無論如何都可以自行完成。坦率地說,這就是所有std :: bind正在做的事情,因爲它正在返回一個模板化的結構,std :: bind只是聲明瞭一個內置了secret_id的新函子。

4

正如6502所解釋的那樣,如果沒有某種上下文參數被傳遞給回調,即使它沒有直接命名secret_id,也無法在便攜式C中執行此操作。但是,有些庫如Bruno Haible's trampoline可以通過非便攜方式創建帶有附加信息(閉包)的C函數。這些庫通過調用匯編或編譯器擴展來實現它們的魔力,但它們被移植到許多流行的平臺上;如果他們支持體系關心,他們工作正常。

the web兩者,這裏是代碼,蹦牀使一個例子是這樣的高階函數,它的參數ab,和c(類似於你secret_id,並返回恰好一個參數x計算a*x^2 + b*x + c的函數:

#include <trampoline.h> 

static struct quadratic_saved_args { 
    double a; 
    double b; 
    double c; 
} *quadratic_saved_args; 

static double quadratic_helper(double x) { 
    double a, b, c; 
    a = quadratic_saved_args->a; 
    b = quadratic_saved_args->b; 
    c = quadratic_saved_args->c; 
    return a*x*x + b*x + c; 
} 

double (*quadratic(double a, double b, double c))(double) { 
    struct quadratic_saved_args *args; 
    args = malloc(sizeof(*args)); 
    args->a = a; 
    args->b = b; 
    args->c = c; 
    return alloc_trampoline(quadratic_helper, &quadratic_saved_args, args); 
} 

int main() { 
    double (*f)(double); 
    f = quadratic(1, -79, 1601); 
    printf("%g\n", f(42)); 
    free(trampoline_data(f)); 
    free_trampoline(f); 
    return 0; 
} 
+0

閉包確實是在「C」中實現此目的的方式。 LibFFI提供閉包,可以移植到大多數主要操作系統:http://sourceware.org/libffi/ –

+0

@BojanNikolic好點,libffi比蹦牀更受歡迎。一個解決OP使用libffi的問題的例子的答案可能會被讚賞。 – user4815162342

3

在源不透明類型和保密應該這樣做:

#include <stdio.h> 

// Secret.h 

typedef struct TagSecret Secret; 
typedef void (*SecretFunction)(Secret*, const char* visible); 
void secret_call(Secret*, const char* visible); 

// Public.c 

void public_action(Secret* secret, const char* visible) { 
    printf("%s\n", visible); 
    secret_call(secret, visible); 
} 


// Secret.c 

struct TagSecret { 
    int id; 
}; 

void secret_call(Secret* secret, const char* visible) { 
    printf("%i\n", secret->id); 
} 

void start() { 
    Secret secret = { 43534 }; 
    public_action(&secret, "Hello World"); 
} 


int main() { 
    start(); 
    return 0; 
} 

(以上內容不涉及註冊回撥函數)