首页 > 解决方案 > 在没有上下文参数的情况下将有状态的 lambda 传递给 C 风格的函数

问题描述

我正在尝试将 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 不再编译。我想这样做是因为我不想使用全局变量。无论如何,这些场景中正式使用的错误是否存在?

据我所知,除了修改库本身之外,没有其他办法。

标签: c++lambdafunction-pointers

解决方案


通常,使用此类回调的 C 库的设计者会提供一些将状态变量与回调相关联的方法。经常void*吵架。一些库在您传递回调时采用该状态指针,并将指针传递给回调函数,例如 WinAPI 通常会这样做,请参阅EnumWindows。其他库允许将该东西放入它们传递给回调的某个对象中,例如 libpng 使用png_set_write_fnAPIpng_get_io_ptr函数来实现。

如果在阅读了可用的文档后,您得出结论认为您的图书馆并非如此,并且您不能或不想向图书馆作者寻求支持,这意味着您必须更具创造力。

一种解决方法是使用哈希映射将文件与容器相关联。像这样:

static std::unordered_map<File*, Container*> s_containers;

之前将文件与容器关联RegisterCallback,并在回调中通过文件指针查找容器。想想线程,也许你需要用互斥锁来保护那个静态哈希映射。还要考虑异常处理,也许您需要 RAII 类在构造函数中注册/在析构函数中注销。

另一种更简单但更有限的方法是使用 C++/11 中引入的 thread_local 存储类说明符,声明

static thread_local Container* s_container;

并在回调中使用它。如果您的库确实阻塞了 IO 并且没有在内部使用线程,那么这很有可能可以正常工作。但是您仍然需要处理错误,即将全局变量重置为nullptr容器超出范围时。

更新:如果您可以更改库,那么这样做比两种解决方法都要好得多。再向 RegisterCallback传递一个void*参数,并将处理程序更改为typedef void(*FileHandler_t)(File* handle, void* context);如果您从 C++ 使用库,通常最好将回调实现为私有静态方法,并将this指针传递到库中。这将允许您在回调中调用实例方法,同时保持类的内部状态隐藏。


推荐阅读