在C中模拟std :: bind

我正在使用std :: bind来提供回调,同时通过首先绑定一些参数来抽象一些逻辑。 即

void start() { int secret_id = 43534; //Bind the secret_id to the callback function object std::function cb = std::bind(&callback, secret_id, std::placeholders::_1); do_action(cb); } void do_action(std::function 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 :: bind那么还有另一种方法将数据从first()传递给callback()而不会使中性do_action()复杂化吗?

C号码不允许你直接这样做。

在C中,处理回调的标准方法是使用上下文指针:

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

这意味着除了回调应该处理的正常参数(在上面的例子中是一个整数)之外,你将传递一个接受void *的函数,你还将传递一个你想要传回的void *

void *通常指向一个struct ,该struct将包含回调中所需的所有额外参数或数据,并且使用此方法,库不依赖于此上下文的内容。 如果回调不需要任何上下文,则只需将NULL指针作为context传递,并在从库中调用时忽略第一个参数。

有点像hackish和forms上不安全的东西,但有时会做的是,如果上下文是一个符合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; } 

正如6502所解释的那样,如果没有将某种上下文参数传递给回调,即使它没有直接命名为secret_id ,也不可能在便携式C中执行此操作。 但是,有一些库,如Bruno Haible的蹦床 ,可以通过非便携方式创建带有附加信息(闭包)的C函数。 这些库通过调用汇编或编译器扩展来发挥其神奇作用,但它们被移植到许多流行的平台上; 如果他们支持关心的架构,他们就可以正常工作。

从网上获取 ,这里是一个trampoline启用的代码示例是这个高阶函数,它接受参数abc (类似于你的secret_id ,并返回一个函数,只有一个参数x计算a*x^2 + b*x + c

 #include  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; } 

最简洁的答案是不。

你唯一能做的就是声明另一个内置了secret_id函数。 如果您正在使用C99或更新版本,您可以使其成为内联函数,以至少限制函数调用开销,尽管较新的编译器可能会自行完成。

坦率地说,这就是std :: bind所做的一切,因为它返回了一个模板化的结构,std :: bind只是声明了一个内置了secret_id的新仿函数。

一个opaque类型并在源代码中保守秘密应该这样做:

 #include  // 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; } 

(以上不涉及注册回调函数)