我应该在C++中使用std :: function还是函数指针?

Jan*_*art 128 c++ function std callback c++11

在C++中实现回调函数时,我是否还应该使用C风格的函数指针:

void (*callbackFunc)(int);
Run Code Online (Sandbox Code Playgroud)

或者我应该使用std :: function:

std::function< void(int) > callbackFunc;
Run Code Online (Sandbox Code Playgroud)

lee*_*mes 158

简而言之,std::function除非你有理由不使用.

函数指针的缺点是无法捕获某些上下文.例如,您将无法将lambda函数作为回调传递来捕获一些上下文变量(但如果它不捕获任何上下文变量,它将起作用).因此,也不可能调用对象的成员变量(即非静态),因为this需要捕获对象(-pointer).(1)

std::function(因为C++ 11)主要是存储一个函数(传递它不需要它存储).因此,如果您想将回调存储在例如成员变量中,那么它可能是您的最佳选择.但是如果你不存储它,它是一个很好的"第一选择",虽然它有一个缺点,即在被调用时引入一些(非常小的)开销(所以在一个非常严重的性能情况下它可能是一个问题,但在大多数情况下它不应该).它非常"通用":如果您非常关注一致且可读的代码,并且不想考虑您所做的每一个选择(即希望保持简单),请使用std::function您传递的每个函数.

考虑第三种选择:如果您要实现一个小函数,然后通过提供的回调函数报告某些内容,请考虑一个模板参数,该参数可以是任何可调用对象,即函数指针,函子,lambda, a std::function,...这里的缺点是你的(外部)函数成为模板,因此需要在头文件中实现.另一方面,您可以获得以下优势:可以内联对回调的调用,因为您的(外部)函数的客户端代码"看到"对回调的调用将提供确切的类型信息.

带有模板参数的版本的示例(写入&而不是&&用于预C++ 11):

template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
    ...
    callback(...);
    ...
}
Run Code Online (Sandbox Code Playgroud)

如下表所示,所有这些都有其优点和缺点:

+-------------------+--------------+---------------+----------------+
|                   | 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)  |               |                |
+-------------------+--------------+---------------+----------------+
Run Code Online (Sandbox Code Playgroud)

(1)存在解决此限制的变通方法,例如将附加数据作为其他参数传递给您的(外部)函数:myFunction(..., callback, data)将调用callback(data).这是C风格的"带参数的回调",这在C++中是可能的(以及在WIN32 API中大量使用的方式),但是应该避免,因为我们在C++中有更好的选择.

(2)除非我们讨论类模板,即存储函数的类是模板.但这意味着在客户端,函数的类型决定了存储回调的对象的类型,这几乎不是实际用例的选项.

(3)对于pre-C++ 11,请使用 boost::function

  • 与模板参数相比,函数指针具有调用开销.模板参数使内联变得容易,即使您传递了十几个级别,因为正在执行的代码由参数的类型而不是值来描述.并且存储在模板返回类型中的模板函数对象是一种常见且有用的模式(使用良好的复制构造函数,您可以创建可调用的高效模板函数,如果需要,可以转换为`std :: function`类型擦除的模板函数将它存储在紧接着的上下文之外). (8认同)

Yak*_*ont 24

void (*callbackFunc)(int); 可能是一个C风格的回调函数,但它是一个糟糕的设计可怕的无法使用.

一个设计良好的C样式回调看起来像void (*callbackFunc)(void*, int);- 它void*允许执行回调的代码保持超出函数的状态.不这样做会迫使调用者全局存储状态,这是不礼貌的.

std::function< int(int) >int(*)(void*, int)在大多数实现中,最终会比invokation 稍贵一些.然而,一些编译器更难以内联.有一些std::function克隆实现可以与可能进入库的函数指针调用开销(参见"最快可能的代理"等)相媲美.

现在,回调系统的客户端通常需要在创建和删除回调时设置资源并处理它们,并且要知道回调的生命周期. void(*callback)(void*, int)不提供这个.

有时这可以通过代码结构(回调具有有限的生命周期)或通过其他机制(取消注册回调等)来获得.

std::function 提供有限的生命周期管理的手段(当忘记对象的最后一个副本消失).

一般情况下,我会使用a,std::function除非表现出性能问题.如果他们这样做,我首先会寻找结构变化(而不是每像素回调,如何基于你传递给我的lambda生成扫描线处理器?这应该足以将函数调用开销减少到微不足道的水平. ).然后,如果它仍然存在,我会写一个delegate基于最快可能的代表,并查看性能问题是否消失.

我主要只使用函数指针用于遗留API,或者用于创建C接口以便在不同编译器生成的代码之间进行通信.当我实现跳转表,类型擦除等时,我也将它们用作内部实现细节:当我同时生成和使用它时,并且我没有在外部公开它以使用任何客户端代码,并且函数指针执行我需要的所有操作.

请注意,假设有适当的回调生命周期管理基础结构,您可以编写将包装std::function<int(int)>转换为int(void*,int)样式回调的包装器.因此,对于任何C风格的回调生命周期管理系统的冒烟测试,我都要确保std::function合理地包装工作.

  • 这个“void*”从哪里来?为什么要在函数之外维持状态?一个函数应该包含它需要的所有代码、所有功能,您只需向它传递所需的参数并修改并返回一些内容。如果您需要一些外部状态,那么为什么 functionPtr 或回调会携带这些行李呢?我认为回调不必要地复杂。 (2认同)

Mik*_*our 17

使用std::function存储任意调用对象.它允许用户提供回调所需的任何上下文; 普通函数指针不会.

如果由于某种原因确实需要使用普通函数指针(也许是因为你需要一个与C兼容的API),那么你应该添加一个void * user_context参数,这样它至少可以(虽然不方便)它访问不直接传递给它的状态.功能.


das*_*ght 14

要避免的唯一理由std::function是支持缺少对此模板的支持的遗留编译器,这已在C++ 11中引入.

如果不需要支持前C++ 11语言,则使用可以std::function让调用者在实现回调时有更多选择,与"普通"函数指针相比​​,它是更好的选择.它为您的API用户提供了更多选择,同时为执行回调的代码抽象出其实现的细节.