在C ++中实现回调函数时,我仍应使用C样式函数指针:
void (*callbackFunc)(int);
或者我应该使用std :: function:
std::function< void(int) > callbackFunc;
在C ++中实现回调函数时,我仍应使用C样式函数指针:
void (*callbackFunc)(int);
或者我应该使用std :: function:
std::function< void(int) > callbackFunc;
Answers:
简而言之,std::function
除非有理由不要使用。
函数指针的缺点是无法捕获某些上下文。例如,您将无法通过lambda函数作为捕获某些上下文变量的回调(但是如果不捕获任何上下文变量,它将起作用)。因此也不可能调用对象的成员变量(即非静态),因为this
需要捕获对象(-pointer)。(1)
std::function
(因为C ++ 11)主要用于存储函数(将其传递就不需要存储它)。因此,例如,如果要将回调存储在成员变量中,则可能是最佳选择。但是,如果您不存储它,则它是一个不错的“首选”,尽管它的缺点是在调用时会引入一些(非常小的)开销(因此,在性能非常关键的情况下,这可能是个问题,但在大多数情况下它不应该)。这是非常“通用的”:如果您非常关心一致且易读的代码,又不想考虑所做的每一个选择(即想保持简单),请使用std::function
传递的每个函数。
考虑第三个选项:如果您要实现一个小的函数,然后通过提供的回调函数报告某些内容,请考虑一个template参数,该参数可以是任何可调用的对象,即函数指针,函子,lambda,一个std::function
缺点是您的(外部)函数成为模板,因此需要在标头中实现。另一方面,您得到的好处是可以内联对回调的调用,因为(外部)函数的客户端代码“看到”了对回调的调用,将提供确切的类型信息。
具有模板参数的版本的示例(对于C ++ 11之前的版本,请写&
而不是&&
):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
正如您在下表中看到的,它们都有优点和缺点:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1)存在克服此限制的解决方法,例如,将其他数据作为进一步的参数传递给您的(外部)函数:myFunction(..., callback, data)
will call callback(data)
。这就是C样式的“带参数的回调”,这在C ++中是可行的(并且在WIN32 API中大量使用),但是应避免使用,因为我们在C ++中有更好的选择。
(2)除非我们在谈论类模板,否则存储函数的类就是模板。但这意味着在客户端,函数的类型决定了存储回调的对象的类型,对于实际用例而言,这几乎不是一个选择。
(3)对于C ++ 11之前的版本,请使用 boost::function
std::function
如果需要将其转换为类型擦除的对象,则可以将其转换为类型擦除的模板函数。称为上下文)。
void (*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
除非表现出对性能的关注,否则我将使用a 。如果是这样,我首先要寻找结构上的变化(而不是每个像素的回调,如何根据传递给我的lambda生成扫描线处理器?这应该足以将函数调用开销减少到微不足道的水平。 )。然后,如果问题仍然存在,我将delegate
根据可能最快的代表编写一个基础,看看性能问题是否消失。
我通常只会将函数指针用于旧版API,或用于创建C接口以在不同编译器生成的代码之间进行通信。在实现跳转表,类型擦除等操作时,我也将它们用作内部实现细节:当我同时生产和使用它时,并且没有在外部公开它以供任何客户端代码使用,而函数指针满足了我的全部需求。
请注意,假设有适当的回调生存期管理基础结构,您可以编写将a std::function<int(int)>
转换为int(void*,int)
样式回调的包装器。因此,作为对任何C风格的回调生命周期管理系统的冒烟测试,我将确保包装std::function
合理工作。
void*
是从哪里来的?您为什么要保持功能之外的状态?函数应该包含它需要的所有代码,所有功能,您只需向其传递所需的参数,然后进行修改并返回某些内容。如果您需要一些外部状态,那为什么functionPtr或callback会携带这些行李呢?我认为回调是不必要的复杂。
this
。是否因为必须考虑调用成员函数的情况,所以我们需要this
指针指向对象的地址?如果我错了,您能否给我一个链接,在该链接中可以找到更多有关此的信息,因为我对此没有太多了解。提前致谢。
void*
来允许传输运行时状态。具有void*
和void*
参数的函数指针可以模拟对对象的成员函数调用。抱歉,我不了解“设计C回调机制101”中涉及的资源。
this
。我正是这个意思。好的,谢谢。
唯一要避免的原因std::function
是对缺少此模板支持的旧版编译器的支持,该模板已在C ++ 11中引入。
如果不需要支持C ++ 11以前的语言,则使用std::function
可使调用者在实现回调时有更多选择,与“普通”函数指针相比,它是更好的选择。它为您的API用户提供了更多选择,同时为执行回调的代码抽象出了其实现的细节。