我正在尝试在使用标准函数指针+上下文范例的C-API中注册一个回调。下面是api的外观:
void register_callback(void(*callback)(void *), void * context);
我真正想做的是能够注册一个C++ lambda作为回调函数。此外,我希望lambda是一个有捕获变量(即。无法转换为直接的无状态std::function
)
我需要编写什么样的适配器代码才能将lambda注册为回调?
发布于 2013-12-12 01:56:08
简单的方法是将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()>([=](){ ... }));
发布于 2013-12-12 02:52:54
最有效的方法是直接对lambda进行voidify
。
#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<void, void(*)(void*)>
中。
如果您想要更复杂的生命周期,可以将unique_ptr<T,D>
s move
d到shared_ptr<T>
中。
上面的代码混合了生存期和数据,类型擦除和实用程序。我们可以分成两部分:
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";
}
发布于 2018-01-18 16:20:22
只要lambda函数没有捕获变量,它就与C-callback函数兼容。
强迫用新的方式把新的东西放到旧的东西上是没有意义的。
用老式的方式怎么样?
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);
https://stackoverflow.com/questions/20525977
复制相似问题