传递本地创建的 lambda 用于回调,然后超出范围

Passing a locally-created lambda for use in a callback, then going out of scope

我正在使用一些涉及回调机制的(有点像 C 语言的)库。我可以提供的回调函数将 void* 作为参数,因此您可以将任意内容传递给它们。为了这个问题,我们假设 lambda 不带任何参数,但它确实捕获了东西。

现在,我需要让我的回调函数调用一个 lambda - 它必须以某种方式通过 void * 获取这个 lambda,即我们有

void my_callback(void * arbitrary_stuff) {
    /* magic... and somehow the lambda passed  */
    /* through `arbitrary_stuff` is invoked.   */
}

// ...

template <T>
void adapted_add_callback(MagicTypeInvolvingT actual_callback) {
    /* more magic */
    libFooAddCallback(my_callback, something_based_on_actual_callback);
}

// ...

void baz();

void bar() {
    int x;
    adapted_add_callback([x]() { /* do something with x */ });
    adapted_add_callback(baz);
}

我想知道用什么替换 magicmore_magicMagicTypeInvolvingT

除了这里的打字挑战,显然,我担心的是如何确保 lambda 封装的数据在堆栈上可用以供最终使用,否则我可能应该进行某种分段错误。

备注:

你有几个问题。

一个是 你不能依赖于将 lambda 本身作为 void * 传递,所以你几乎需要传递一个指向 lambda 的指针(嗯,从 lambda 创建的闭包,如果你想要精确的话)。这意味着您需要确保 lambda 在回调完成之前保持有效。

第二个问题是关于这些捕获如何发生的问题 - 按值捕获还是按引用捕获?如果按值捕获,一切都很好。如果您通过引用捕获,您还需要确保您捕获的任何内容在回调完成之前仍然有效。如果您通过引用捕获一个全局变量,那通常应该没问题——但是如果您通过引用捕获一个局部变量,那么在调用 lambda 之前,局部变量(甚至可能)超出范围,使用引用将导致未定义的行为。

最直接的方法可能是(假设 C 函数保证只调用一次回调,并且 lambda 在回调点有效)

void my_callback(void * arbitrary_stuff) {
    (*std::unique_ptr{ static_cast<std::function<void()>*>(arbitrary_stuff) })();
}

void adapted_add_callback( std::function<void()> actual_callback ) {
    libFooAddCallback(my_callback, new auto( std::move(actual_callback) ) );
}

如果您不想要函数<>开销,您将需要实现自己的类型擦除...

我采用了类似于 的方式,但没有 std::function 的开销。您必须确保库只调用一次回调。

using Callback = void(*)(void*);


// Probes the type of the argument and generates a suitable cast & invoke stub
// Caution: self-destructs after use!
template <class F>
Callback cbkWrap(F &) {
    return [](void *data) {
        std::unique_ptr<F> retrieved(static_cast<F*>(data));
        (*retrieved)();
    };
}

// Moves the functor into a dynamically-allocated one
template <class F>
void *cbkFunc(F &f) {
    return new F{std::move(f)};
}

int main() {
    int x = 42;

    auto lambda = [&x] { std::cout << x << '\n'; };

    libFooAddCallback(cbkWrap(lambda), cbkFunc(lambda));
}

See it live on Coliru

如果你能确保 lambda 比潜在调用更有效,你就可以摆脱动态内存分配并简单地传递一个指向它的指针 data:

// Probes the type of the argument and generates a suitable cast & invoke stub
template <class F>
Callback cbkWrap(F &) {
    return [](void *data) {
        auto retrieved = static_cast<F*>(data);
        (*retrieved)();
    };
}

int main() {
    int x = 42;

    auto lambda = [&x] { std::cout << x << '\n'; };

    libFooAddCallback(cbkWrap(lambda), &lambda);
}

See it live on Coliru

不幸的是,如果不知道它会被调用多少次,就无法将 lamba 的所有权赋予库。