我正在尝试将C库集成到我的C ++项目中。 C库具有以函数指针作为参数的函数,但是这些函数指针被写为typedef。
typedef void(*FileHandler_t)(File* handle);
然后使用一个函数注册回调,如下所示:
void RegisterCallback(FileHandler_t handler);
我可以创建一个lambda表达式,并将其传递给RegisterCallback作为参数处理程序
auto handler = [](File* handle){ //handle cb };
这很好。
RegisterCallback(handler);
但是当我尝试传递局部变量以在处理程序内部使用时,我会收到编译器错误。
auto handler = [&container](File* handle){ //handle cb };
现在RegisterCallback不再编译。 我想这样做,因为我不想使用全局变量。反过来在这些情况下会正式使用该错误吗?
据我所知,除了修改库本身之外,别无选择。
答案 0 :(得分:4)
通常,使用此类回调的C库设计人员提供了某种将状态变量与回调关联的方法。通常为void*
参数。某些库在传递回调时采用该状态指针,然后将指针传递给回调函数,例如WinAPI通常会这样做,请参见EnumWindows。其他库允许将该内容放入它们传递给回调的某个对象中,例如libpng使用png_set_write_fn
和png_get_io_ptr
API函数来做到这一点。
如果在阅读了可用的文档后得出结论,认为图书馆不是这种情况,并且您不能或不想向图书馆作者寻求支持,则意味着您必须更具创造力。
一种解决方法是使用哈希映射将文件与容器关联。像这样:
static std::unordered_map<File*, Container*> s_containers;
在RegisterCallback
之前将文件与容器相关联,并在回调中通过文件指针查找以找出容器。考虑线程问题,也许您需要使用互斥量来保护该静态哈希映射。再考虑一下异常处理,也许您需要RAII类在构造函数中注册/在析构函数中取消注册。
另一种更简单但更受限制的方法是使用C ++ / 11中引入的thread_local存储类说明符,声明
static thread_local Container* s_container;
并在回调中使用它。如果您的媒体库确实阻止了IO,并且未在内部使用线程,则很有可能可以正常运行。但是仍然需要处理错误,即当容器超出范围时将全局变量重置为nullptr
。
更新:如果您可以更改库,则这样做比两种解决方法都好得多。将另一个void*
参数传递给RegisterCallback,并将处理程序更改为typedef void(*FileHandler_t)(File* handle, void* context);
如果您正在使用C ++中的库,通常最好将回调实现为私有静态方法,并传递{{1} }指向库的指针。这将允许您在回调中调用实例方法,同时保持类的内部状态隐藏。
答案 1 :(得分:0)
Lambda通常具有您不能依赖的类型;仅在它们什么都没捕获的情况下,类型“ Gantry”(等于)才是函数指针。
最常见的解决方法是改为在lambda中使用静态数据:
#include <iostream>
#include <vector>
typedef void(*FuncInt)(int);
static bool DataTheFunctionNeeds = true;
int main() {
FuncInt a = [](int) { };
FuncInt b = [](int) { if(DataTheFunctionNeeds){} };
}
答案 2 :(得分:0)
如前所述,在回调函数中通常没有用于标识的void*
指针。
但是,作为一种解决方法,您可以引入一个类模板来为您存储此信息。 live demo:
template<typename FN>
class dispatcher {
static inline std::function<void(int)> fn_;
static void foo(int x) { fn_(x); }
public:
static fn_type dispatch(FN fn) {
fn_ = fn;
return &dispatcher::foo;
}
};
该类是用于解决成员函数fn_
的歧义的模板。
但是,这在类上不能很好地工作。但是您可以更改它,以便提供某种自己的“歧义消除器”。 live demo:
template<int DSP>
class dispatcher {
static inline std::function<void(int)> fn_;
static void foo(int x) { fn_(x); }
public:
template<typename FN>
static fn_type dispatch(FN fn) {
fn_ = fn;
return &dispatcher::foo;
}
};
答案 3 :(得分:0)
由于lambda具有状态,因此将此类lambda转换为函数指针将涉及将lambda状态存储在具有静态存储持续时间的变量中。
这显然很容易出错,因为在每次转换为函数指针时,都会修改静态存储持续时间状态,从而导致很难跟踪错误。这肯定是为什么带状态的lambda无法转换为函数指针的原因。
反之,如果您了解有什么危险,则可以实现静态存储时间lambda转换。 (您必须确保在转换为函数指针之后,不会访问先前转换的lambda实例):
#include <utility>
#include <new>
//bellow, a more efficient version of std::function
//for holding (statically) typed lambda of static storage duration
//Because lamda type depends on the function in which they are defined
//this is much more safer than a naive implementation using std::function.
template<class LT>
struct static_lambda{
private:
static inline unsigned char buff alignas(LT) [sizeof(LT)];
static inline LT* p=nullptr;
static inline struct raii_guard{
~raii_guard(){
if (p) p->~LT();
}
} guard{};
public:
static_lambda(LT g_){
if (p) p->~LT();
p=new (buff) LT{std::move(g_)};
}
static_lambda(const static_lambda&)=delete //for safety
template<class...Args>
static auto execute(Args...args){
return (*p)(std::forward<Args>(args)...);
}
template<class FT>
operator FT*() && {//by rvalue reference for safety reason
return execute;
}
};
using File = int;
typedef void(*FileHandler_t)(File* handle);
void RegisterCallback(FileHandler_t handler);
using container_type=double*;
void test(container_type& container){
//each time this function is called
//the previously registered lambda is destroyed
//and should not be accessed (which is certainly the case).
RegisterCallback(static_lambda{[&container](File* h){}});
}