6

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

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

4 回答 4

12

不,C 不允许你直接这样做。

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

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

这意味着void *除了回调应该处理的普通参数(在上述情况下为整数)之外,您还将传递一个函数,该函数将接受 a ,并且您还将传递void *您想要传回的 a 。

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

有点骇人听闻且形式上不安全但有时会这样做的事情是,如果上下文是适合 a 大小的简单数据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;
}
于 2013-09-27T08:28:44.493 回答
4

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

取自网络,这里有一个 trampoline 启用的代码示例,它是这个高阶函数,它接受参数a,bc(类似于你的, 并返回一个只计算secret_id一个参数的函数:xa*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;
}
于 2013-09-27T08:44:44.203 回答
3

最简洁的答案是不。

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

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

于 2013-09-27T08:34:33.590 回答
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;
}

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

于 2013-09-27T10:00:31.790 回答