如何将C++ lambda传递给需要函数指针和上下文的C-callback?

Mic*_*hop 30 c c++ lambda c++11

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

void register_callback(void(*callback)(void *), void * context);
Run Code Online (Sandbox Code Playgroud)

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

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

Die*_*ühl 21

简单的aporoach是将lambda粘在一个std::function<void()>保存在某处的地方.它可能在堆上分配,仅由void*注册的实体引用回调引用.那么回调就是这样的函数:

extern "C" void invoke_function(void* ptr) {
    (*static_cast<std::function<void()>*>(ptr))();
}
Run Code Online (Sandbox Code Playgroud)

注意,std::function<S>可以保存具有状态的函数对象,例如具有非空捕获的lambda函数.您可以注册这样的回调:

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


Yak*_*ont 11

最有效的方法是voidify直接使用lambda.

#include <iostream>
#include <tuple>
#include <memory>

template<typename... Args, typename Lambda>
std::pair< void(*)(void*, Args...), std::unique_ptr<void, void(*)(void*)> > voidify( Lambda&& l ) {
  typedef typename std::decay<Lambda>::type Func;
  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.first, voidified.second.get() );
  register_callback( voidified.first, voidified.second.get() );
  std::cout << x << "\n";
}
int main() {
  test();
}
Run Code Online (Sandbox Code Playgroud)

这里voidify有一个lambda和(可选)参数列表,并生成一个传统的C风格的回调void*对.它void*由一个unique_ptr带有特殊删除器的人拥有,因此它的资源得到了适当的清理.

这比std::function解决方案的优点是效率 - 我消除了一个级别的运行时间接.回调有效的生命周期也很明确,因为它在std::unique_ptr<void, void(*)(void*)>返回的中voidify.

unique_ptr<T,D>如果你想要更复杂的生命周期,可以move进入shared_ptr<T>.


以上内容将数据与生命周期混合在一起,并使用实用程序进行类型擦除.我们可以拆分它:

template<typename... Args, typename Lambda>
std::pair< void(*)(void*, Args...), std::decay_t<Lambda> > voidify( Lambda&& l ) {
  typedef typename std::decay<Lambda>::type Func;
  return {
    +[](void* v, Args... args)->void {
      Func* f = static_cast< Func* >(v);
      (*f)(std::forward<Args>(args)...);
    },
    std::forward<Lambda>(l)
  };
}
Run Code Online (Sandbox Code Playgroud)

现在voidify不分配.只需保存你voidify回调的寿命,传递指针,TO- second作为void*沿一侧的first函数指针.

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

  • 我不确定是否对“ voidified”一词印象深刻或恐惧 (3认同)

Hil*_*ill 7

只要 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);
Run Code Online (Sandbox Code Playgroud)