我正在尝试在使用标准函数指针+上下文范例的C-API中注册回调。这是api的样子:
void register_callback(void(*callback)(void *), void * context);
我真正想做的是能够注册C ++ lambda作为回调。另外,我希望lambda是一个捕获变量的(即无法转换为直接无状态std::function
)
我需要编写哪种适配器代码才能将lambda注册为回调?
答案 0 :(得分:19)
简单的aporoach是将lambda粘贴到保留在某处的std::function<void()>
。它可能在堆上分配,仅由注册了回调实体的void*
引用。那么回调就是这样一个函数:
extern "C" void invoke_function(void* ptr) {
(*static_cast<std::function<void()>*>(ptr))();
}
注意std::function<S>
可以保存具有状态的函数对象,例如具有非空捕获的lambda函数。您可以注册这样的回调:
register_callback(&invoke_function,
new std::function<void()>([=](){ ... }));
答案 1 :(得分: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();
}
这里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)
};
}
现在voidify
没有分配。只需在回调的生命周期内存储voidify,并将second
指针作为void*
传递给first
函数指针。
如果您需要将此构造存储在堆栈中,将lambda转换为std::function
可能会有所帮助。或者使用上面的第一个变体。
答案 2 :(得分:2)
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);
答案 3 :(得分:0)
使用lamda函数作为C函数指针的一种非常简单的方法是:
auto fun=+[](){ //add code here }
请参阅此答案以获取示例。 https://stackoverflow.com/a/56145419/513481