【问题标题】:How can I pass a C++ lambda to a C-callback that expects a function pointer and a context?如何将 C++ lambda 传递给需要函数指针和上下文的 C 回调?
【发布时间】:2013-12-11 17:30:22
【问题描述】:

我正在尝试在使用标准函数指针+上下文范例的 C-API 中注册回调。这是 api 的样子:

void register_callback(void(*callback)(void *), void * context);

我真正想做的是能够注册一个 C++ lambda 作为回调。此外,我希望 lambda 能够捕获变量(即不能转换为直接无状态 std::function

我需要编写什么样的适配器代码才能将 lambda 注册为回调?

【问题讨论】:

  • 虽然 std::function 是一种能够保存 lambda 表达式的类型擦除机制,但它不是 lambda
  • 这很公平。差异是否使您无法理解问题?有什么办法可以改写吗?
  • 它可能是,我没有进一步考虑这个问题对你完全诚实,但你可以重写说你需要将函数指针拉出 std::function 而不是比超出 lambda
  • 听起来不错。有没有办法从中提取上下文?我可以将某种状态作为上下文指针传递给 C-api?

标签: c++ c c++11 lambda


【解决方案1】:

简单的方法是将 lambda 粘贴到 std::function<void()> 中,该 std::function<void()> 保存在某处。它可能是在堆上分配的,并且仅由注册到接受回调的实体的void* 引用。回调将只是这样的函数:

extern "C" void invoke_function(void* ptr) {
    (*static_cast<std::function<void()>*>(ptr))();
}

请注意,std::function&lt;S&gt; 可以保存具有状态的函数对象,例如具有非空捕获的 lambda 函数。你可以像这样注册一个回调:

register_callback(&invoke_function,
  new std::function<void()>([=](){ ... }));

【讨论】:

    【解决方案2】:

    最有效的方法是直接voidify lambda。

    #include <iostream>
    #include <tuple>
    #include <memory>
    
    template<class...Args>
    struct callback {
      void(*function)(void*, Args...)=nullptr;
      std::unique_ptr<void, void(*)(void*)> state;
    };
    template<typename... Args, typename Lambda>
    callback<Args...> voidify( Lambda&& l ) {
      using Func = typename std::decay<Lambda>::type;
      std::unique_ptr<void, void(*)(void*)> data(
        new Func(std::forward<Lambda>(l)),
        +[](void* ptr){ delete (Func*)ptr; }
      );
      return {
        +[](void* v, Args... args)->void {
          Func* f = static_cast< Func* >(v);
          (*f)(std::forward<Args>(args)...);
        },
        std::move(data)
      };
    }
    
    void register_callback( void(*function)(void*), void * p ) {
      function(p); // to test
    }
    void test() {
      int x = 0;
      auto closure = [&]()->void { ++x; };
      auto voidified = voidify(closure);
      register_callback( voidified.function, voidified.state.get() );
      register_callback( voidified.function, voidified.state.get() );
      std::cout << x << "\n";
    }
    int main() {
      test();
    }
    

    这里 voidify 接受一个 lambda 和(可选)一个参数列表,并生成一个传统的 C 样式回调-void* 对。 void* 由带有特殊删除器的 unique_ptr 拥有,因此其资源被正确清理。

    std::function 解决方案相比,它的优势在于效率——我消除了一级运行时间接。回调有效的生命周期也很清楚,因为它在voidify 返回的std::unique_ptr&lt;void, void(*)(void*)&gt; 中。

    如果您想要更复杂的生命周期,unique_ptr&lt;T,D&gt;s 可以将 moved 转换为 shared_ptr&lt;T&gt;


    上面将生命周期与数据混合在一起,将类型擦除与实用程序混合在一起。我们可以拆分它:

    template<class Lambda, class...Args>
    struct callback {
      void(*function)(void*, Args...)=nullptr;
      Lambda state;
    };
    
    template<typename... Args, typename Lambda>
    callback<typename std::decay<Lambda>::type, Args...> voidify( Lambda&& l ) {
      using Func = typename std::decay<Lambda>::type;
      return {
        +[](void* v, Args... args)->void {
          Func* f = static_cast< Func* >(v);
          (*f)(std::forward<Args>(args)...);
        },
        std::forward<Lambda>(l)
      };
    }
    

    现在voidify 没有分配。只需在回调的生命周期内存储您的 voidify,将指向second 的指针作为void*first 函数指针一起传递。

    如果您需要将此构造存储在堆栈之外,将 lambda 转换为 std::function 可能会有所帮助。或者使用上面的第一个变体。

    void test() {
      int x = 0;
      auto closure = [&]()->void { ++x; };
      auto voidified = voidify(closure);
      register_callback( voidified.function, &voidified.state );
      register_callback( voidified.function, &voidified.state );
      std::cout << x << "\n";
    }
    

    【讨论】:

    • 我不确定我是否对“无效化”这个词印象深刻或感到恐惧
    • 有没有人成功让voidify 用于具有多个参数的回调签名?当我在闭包和回调函数签名中添加参数时,类型系统总是认为voidified.first的类型是void (*)(void*)
    • @ChrisCleeland 你必须明确地传入&lt;Args...&gt;,它不能从 lambda 推导出来。这是你的问题吗?
    • 啊。因此,如果回调签名是void(*)(int, long long),则需要使用voidify&lt;int, long long&gt;(closure)auto closure = [=](int, long long) { ... }
    • @ChrisCleeland 对于回调void(*)(void*, int, long long),您致电auto cb = voidify&lt;int, long long&gt;([&amp;](int x, long long l){ /* code */ });。要使 lambda 可用作回调,回调必须是 void* 用户数据和带函数的 void* 对。如果 C 样式回调采用“userdata”void* 而非第一个参数,您还必须修改 voidify
    【解决方案3】:

    只要没有捕获变量,lambda 函数就与 C 回调函数兼容。
    强迫用新的方式把新的东西放在旧的东西上是没有意义的。
    走老路怎么样?

    typedef struct
    {
      int cap_num;
    } Context_t;
    
    int cap_num = 7;
    
    Context_t* param = new Context_t;
    param->cap_num = cap_num;   // pass capture variable
    register_callback([](void* context) -> void {
        Context_t* param = (Context_t*)context;
        std::cout << "cap_num=" << param->cap_num << std::endl;
    }, param);
    

    【讨论】:

      【解决方案4】:

      将 lamda 函数用作 C 函数指针的一种非常简单的方法是:

      auto fun=+[](){ //add code here }
      

      请参阅此答案以获取示例。 https://stackoverflow.com/a/56145419/513481

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多