在C++中实现回调函数时,我是否仍应该使用C风格的函数指针:
void (*callbackFunc)(int);
或者我应该使用std::function:
std::function< void(int) > callbackFunc;
在C++中实现回调函数时,我是否仍应该使用C风格的函数指针:
void (*callbackFunc)(int);
或者我应该使用std::function:
std::function< void(int) > callbackFunc;
std::function
。this
指针)(1)。
std::function
(自C++11起)主要用于存储函数(传递函数时不需要存储)。因此,如果您想在数据成员中存储回调函数,这可能是您最好的选择。但即使您不存储它,它也是一个很好的“首选”,尽管在调用时会引入一些(非常小的)开销(因此在非常性能关键的情况下可能会有问题,但在大多数情况下应该没有问题)。它非常“通用”:如果您非常关心一致且可读的代码,并且不想考虑每个选择(即希望保持简单),则对于传递的每个函数都可以使用std::function
。
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
函数指针 | std::function | 模板参数 | |
---|---|---|---|
能捕获上下文变量 | 否1 | 是 | 是 |
没有调用开销(见注释) | 是 | 否 | 是 |
可以内联(见注释) | 否 | 否 | 是 |
可以存储在类成员中 | 是 | 是 | 否2 |
可以在头文件之外实现 | 是 | 是 | 否 |
支持非C++11标准 | 是 | 否3 | 是 |
易读(个人观点) | 否 | 是 | (是) |
boost::function
。std::function
类型擦除)。 - Yakk - Adam Nevraumontusing functionptr_f = std::add_pointer_t<bool(void*, std::string const&, int)>
- 这将声明一个函数指针,相当于旧语法中的 typedef void (* functionptr_f)(void*, std::string const&, int)
。我发现using
形式更加易读和表达性强; 它还有一个优点,就是可以通过将 add_pointer_t
替换为 function
来轻松转换为 std :: function
声明。 - fish2000void (*callbackFunc)(int)
可能是一个C风格的回调函数,但它是设计很差且难以使用的。
一个良好设计的C风格回调函数应该长这样:void (*callbackFunc)(void*, int)
-- 它有一个 void*
用于让执行回调的代码在函数之外维护状态。不这样做会强制调用者在全局存储状态,这是不礼貌的。
std::function< int(int) >
在大多数实现中比 int(*)(void*, int)
调用略微更昂贵。然而,对于某些编译器来说,它更难进行内联优化。有一些 std::function
的克隆实现可以与函数指针调用相媲美(参见“最快的代理”等),这些实现可能会进入库中。
现在,回调系统的客户端通常需要在创建和删除回调时设置资源并处置它们,并且需要了解回调的生命周期。void(*callback)(void*, int)
不能提供此功能。
有时通过代码结构(回调的生命周期有限)或通过其他机制(注销回调等)可用。
std::function
提供了有限生命周期管理的手段(当最后一个对象被遗忘时它消失)。
总的来说,除非存在性能问题,我会使用 std::function
。如果存在性能问题,我首先会寻找结构性的变化(例如,不是逐像素回调,而是基于你传给我的lambda生成扫描线处理器?这应该足以将函数调用开销降到微不足道的水平)。然后,如果问题仍然存在,我会编写一个基于最快代理的委托,并看看性能问题是否得到解决。
我通常只在遗留的API或为不同编译器生成的代码之间创建C接口时使用函数指针。当我需要实现跳转表、类型擦除等内部细节时,我会使用它们:当我既要生产又要消费时,并且不向任何客户端代码公开,函数指针就够用了。
请注意,您可以编写包装器将std::function<int(int)>
转换为int(void *,int)
样式的回调,假设有适当的回调生命周期管理基础结构。因此,作为任何C风格回调生命周期管理系统的烟雾测试,我会确保对std::function
进行包装工作得相当好。
void*
是从哪里来的?为什么你想要在函数之外维护状态?一个函数应该包含它所需的所有代码和功能,你只需要传递所需的参数并修改和返回一些东西。如果你需要一些外部状态,那么为什么函数指针或回调要承载这个负担呢?我认为回调是不必要复杂的。 - KeyC0devoid*
以允许传输运行时状态。带有void*
和void*
参数的函数指针可以模拟对对象的成员函数调用。很抱歉,我不知道有哪些资源可以介绍“设计C回调机制101”。 - Yakk - Adam Nevraumontthis
。这就是我所指的。好的,无论如何还是谢谢。 - KeyC0devoid*
指向一个 int
的指针以记录回调被调用的次数,例如: void increment_int(void* ptr){ int* pint = static_cast<int*>(ptr); ++*pint; }
。或者您可以定义一个 struct counter { int x; void increment() { ++x; } }; void counter_increment( void* ptr )
。 - Yakk - Adam Nevraumontvoid*
使其类型不安全,但在通过不具备类型感知能力的C API时,某些类型不安全是不可避免的。 - Yakk - Adam Nevraumont使用std::function
来存储任意可调用对象。它允许用户提供回调所需的任何上下文;而普通函数指针则不行。
如果出于某些原因确实需要使用普通函数指针(例如,因为您想要与C兼容的API),那么应该添加一个void * user_context
参数,以便它至少可以(尽管不方便)访问未直接传递给函数的状态。
避免使用std::function
的唯一原因是支持旧版本编译器。这些编译器不支持在C++11中引入的该模板。
如果不需要支持C++11之前的语言版本,则与“纯”函数指针相比,使用std::function
可以让调用者在实现回调时拥有更多选择,从而成为更好的选项。它为您API的用户提供了更多的选择,同时将其实现的细节抽象出来,使您的执行回调的代码更加通用。
std::function
在某些情况下可能会给代码带来虚函数表,这对性能有一定影响。
有一种情况适合使用普通的C函数指针:比较。
std::function没有恰当的(不)相等概念。唯一支持的比较是与nullptr,也就是“空”状态的比较。而函数指针只是指针,所以“==”可以正常工作并且做你通常想要的事情:对于指向完全相同代码的回调返回true(或者都为null),否则返回false。因此,如果您确实需要支持比较回调函数,则需要使用普通的旧函数指针或自定义函数对象类型(一个带有“operator()”的结构体),而不是std::function。