在C中模拟std :: bind

时间:2013-09-27 08:19:44

标签: c++ c c++11 function-pointers

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

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 :: bind,那么还有另一种方法可以将数据从first()传递给callback()而不会使中性do_action()复杂化吗?

4 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:4)

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

取自the web,以下是trampoline启用的代码示例,这是一个带有参数abc的高阶函数(类似于您的secret_id,并返回一个只计算x的{​​{1}}参数的函数:

a*x^2 + b*x + c

答案 2 :(得分:3)

简短的回答是否定的。

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

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

答案 3 :(得分:3)

opaque类型并在源代码中保密:

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

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