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

时间:2021-10-04 18:53:06

When implementing a callback function in C++, should I still use the C-style function pointer:

在c++中实现回调函数时,是否仍然使用C风格的函数指针:

void (*callbackFunc)(int);

Or should I make use of std::function:

或者我应该使用std::

std::function< void(int) > callbackFunc;

5 个解决方案

#1


134  

In short, use std::function unless you have a reason to not.

简而言之,使用std::函数,除非你有理由不这么做。

Function pointers have the disadvantage of not being able to capture some context. You won't be able to for example pass a lambda function as a callback which captures some context variables (but it will work if it doesn't capture any). Calling a member variable of an object (i.e. non-static) is thus also not possible, since the object (this-pointer) needs to be captured.(1)

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

std::function (since C++11) is primarily to store a function (passing it around doesn't require it to be stored). Hence if you want to store the callback for example in a member variable, it's probably your best choice. But also if you don't store it, it's a good "first choice" although it has the disadvantage of introducing some (very small) overhead when being called (so in a very performance-critical situation it might be a problem but in most it should not). It is very "universal": if you care a lot about consistent and readable code as well as don't want to think about every choice you make (i.e. want to keep it simple), use std::function for every function you pass around.

函数(因为c++ 11)主要是存储一个函数(传递它并不需要它被存储)。因此,如果您想将回调存储在一个成员变量中,这可能是您最好的选择。但是如果您不存储它,这也是一个很好的“首选”,尽管在调用时引入一些(非常小的)开销(因此在性能关键的情况下,这可能是一个问题,但在大多数情况下不应该如此)。它是非常“通用”的:如果您非常关心一致和可读的代码,并且不希望考虑您所做的每一个选择(例如,希望保持简单),那么对您传递的每个函数使用std:::function。

Think about a third option: If you're about to implement a small function which then reports something via the provided callback function, consider a template parameter, which can then be any callable object, i.e. a function pointer, a functor, a lambda, a std::function, ... Drawback here is that your (outer) function becomes a template and hence needs to be implemented in the header. On the other hand you get the advantage that the call to the callback can be inlined, as the client code of your (outer) function "sees" the call to the callback will the exact type information being available.

考虑第三个选项:如果您打算实现一个小函数,然后通过提供的回调函数报告某些内容,请考虑一个模板参数,它可以是任何可调用对象,例如一个函数指针、一个函数、一个lambda、一个std::::function……这里的缺点是(外部)函数变成了模板,因此需要在头中实现。另一方面,由于(外部)函数的客户端代码“看到”回调的调用将提供确切的类型信息,因此可以内联回调的调用。

Example for the version with the template parameter (write & instead of && for pre-C++11):

使用模板参数的版本示例(编写&而不是&用于c++ 11):

template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
    ...
    callback(...);
    ...
}

As you can see in the following table, all of them have their advantages and disadvantages:

如下表所示,它们各有利弊:

+-------------------+--------------+---------------+----------------+
|                   | 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) Workarounds exist to overcome this limitation, for example passing the additional data as further parameters to your (outer) function: myFunction(..., callback, data) will call callback(data). That's the C-style "callback with arguments", which is possible in C++ (and by the way heavily used in the WIN32 API) but should be avoided because we have better options in C++.

(1)解决方案的存在可以克服这个限制,例如将附加的数据作为进一步的参数传递给您的(外部)函数:myFunction(…),回调,数据)将调用回调(数据)。这是C风格的“带参数的回调”,在c++中是可能的(顺便提一下,WIN32 API中使用得很频繁),但是应该避免,因为我们在c++中有更好的选项。

(2) Unless we're talking about a class template, i.e. the class in which you store the function is a template. But that would mean that on the client side the type of the function decides the type of the object which stores the callback, which is almost never an option for actual use cases.

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

(3) For pre-C++11, use boost::function

(3)对于pre- c++ 11,使用boost::::函数

#2


21  

void (*callbackFunc)(int); may be a C style callback function, but it is a horribly unusable one of poor design.

空白(* callbackFunc)(int);可能是一个C风格的回调函数,但它是一个糟糕的无用设计。

A well designed C style callback looks like void (*callbackFunc)(void*, int); -- it has a void* to allow the code that does the callback to maintain state beyond the function. Not doing this forces the caller to store state globally, which is impolite.

设计良好的C风格回调看起来像void (*callbackFunc)(void*, int);——它有一个void*,允许执行回调的代码维护函数之外的状态。不这样做会迫使调用者在全局存储状态,这是不礼貌的。

std::function< int(int) > ends up being slightly more expensive than int(*)(void*, int) invokation in most implementations. It is however harder for some compilers to inline. There are std::function clone implementations that rival function pointer invokation overheads (see 'fastest possible delegates' etc) that may make their way into libraries.

函数< int(int) >在大多数实现中都比int(*)(void*, int)调用稍微贵一些。然而,对于某些编译器来说,内联却是比较困难的。有一些std:::函数克隆实现可以与函数指针调用开销(参见“最快的可能委托”等)相竞争,这些实现可以进入库。

Now, clients of a callback system often need to set up resources and dispose of them when the callback is created and removed, and to be aware of the lifetime of the callback. void(*callback)(void*, int) does not provide this.

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

Sometimes this is available via code structure (the callback has limited lifetime) or through other mechanisms (unregister callbacks and the like).

有时这可以通过代码结构(回调具有有限的生存期)或其他机制(取消注册回调等)来实现。

std::function provides a means for limited lifetime management (the last copy of the object goes away when it is forgotten).

函数为有限的生命周期管理提供了一种方法(当对象被遗忘时,最后一个副本将消失)。

In general, I'd use a std::function unless performance concerns manifest. If they did, I'd first look for structural changes (instead of a per-pixel callback, how about generating a scanline processor based off of the lambda you pass me? which should be enough to reduce function-call overhead to trivial levels.). Then, if it persists, I'd write a delegate based off fastest possible delegates, and see if the performance problem goes away.

通常,我将使用std::函数,除非性能问题出现。如果他们这样做了,我将首先查找结构更改(而不是每个像素的回调,那么基于您传递给我的lambda生成一个扫描线处理器怎么样?)这应该足以将函数调用开销降低到微不足道的级别)。然后,如果它继续存在,我将根据最快的委托编写一个委托,看看性能问题是否消失。

I would mostly only use function pointers for legacy APIs, or for creating C interfaces for communicating between different compilers generated code. I have also used them as internal implementation details when I am implementing jump tables, type erasure, etc: when I am both producing and consuming it, and am not exposing it externally for any client code to use, and function pointers do all I need.

我通常只对遗留api使用函数指针,或者创建用于在不同编译器生成的代码之间通信的C接口。当我实现跳转表、类型擦除等时,我还将它们用作内部实现细节:当我同时生产和使用它,并且不将它公开给任何客户端代码使用时,函数指针完成了我所需要的一切。

Note that you can write wrappers that turn a std::function<int(int)> into a int(void*,int) style callback, assuming there are proper callback lifetime management infrastructure. So as a smoke test for any C-style callback lifetime management system, I'd make sure that wrapping a std::function works reasonably well.

注意,您可以编写包装器,将std::函数 转换为int(void*,int)风格的回调,前提是有合适的回调生命周期管理基础设施。因此,作为对任何c风格回调生命周期管理系统的冒烟测试,我将确保包装std:::函数工作得相当好。 (int)>

#3


16  

Use std::function to store arbitrary callable objects. It allows the user to provide whatever context is needed for the callback; a plain function pointer does not.

函数的作用是:存储任意可调用对象。它允许用户提供回调所需的任何上下文;普通函数指针则不是。

If you do need to use plain function pointers for some reason (perhaps because you want a C-compatible API), then you should add a void * user_context argument so it's at least possible (albeit inconvenient) for it to access state that's not directly passed to the function.

如果出于某种原因需要使用普通函数指针(可能是因为需要c兼容的API),那么应该添加一个void * user_context参数,这样至少可以(尽管不方便)访问没有直接传递给函数的状态。

#4


13  

The only reason to avoid std::function is support of legacy compilers that lack support for this template, which has been introduced in C++11.

避免std::函数的唯一原因是支持不支持这个模板的遗留编译器,c++ 11引入了这个模板。

If supporting pre-C++11 language is not a requirement, using std::function gives your callers more choice in implementing the callback, making it a better option compared to "plain" function pointers. It offers the users of your API more choice, while abstracting out the specifics of their implementation for your code that performs the callback.

如果不需要支持pre-C+ 11语言,那么使用std:::函数可以让调用者在实现回调时有更多的选择,这使它比“普通”函数指针更好。它为您的API提供了更多的选择,同时抽象出执行回调的代码的实现细节。

#5


0  

std::function may bring VMT to the code in some cases, which has some impact on performance.

在某些情况下,函数可以将VMT带入到代码中,这对性能有一定的影响。

#1


134  

In short, use std::function unless you have a reason to not.

简而言之,使用std::函数,除非你有理由不这么做。

Function pointers have the disadvantage of not being able to capture some context. You won't be able to for example pass a lambda function as a callback which captures some context variables (but it will work if it doesn't capture any). Calling a member variable of an object (i.e. non-static) is thus also not possible, since the object (this-pointer) needs to be captured.(1)

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

std::function (since C++11) is primarily to store a function (passing it around doesn't require it to be stored). Hence if you want to store the callback for example in a member variable, it's probably your best choice. But also if you don't store it, it's a good "first choice" although it has the disadvantage of introducing some (very small) overhead when being called (so in a very performance-critical situation it might be a problem but in most it should not). It is very "universal": if you care a lot about consistent and readable code as well as don't want to think about every choice you make (i.e. want to keep it simple), use std::function for every function you pass around.

函数(因为c++ 11)主要是存储一个函数(传递它并不需要它被存储)。因此,如果您想将回调存储在一个成员变量中,这可能是您最好的选择。但是如果您不存储它,这也是一个很好的“首选”,尽管在调用时引入一些(非常小的)开销(因此在性能关键的情况下,这可能是一个问题,但在大多数情况下不应该如此)。它是非常“通用”的:如果您非常关心一致和可读的代码,并且不希望考虑您所做的每一个选择(例如,希望保持简单),那么对您传递的每个函数使用std:::function。

Think about a third option: If you're about to implement a small function which then reports something via the provided callback function, consider a template parameter, which can then be any callable object, i.e. a function pointer, a functor, a lambda, a std::function, ... Drawback here is that your (outer) function becomes a template and hence needs to be implemented in the header. On the other hand you get the advantage that the call to the callback can be inlined, as the client code of your (outer) function "sees" the call to the callback will the exact type information being available.

考虑第三个选项:如果您打算实现一个小函数,然后通过提供的回调函数报告某些内容,请考虑一个模板参数,它可以是任何可调用对象,例如一个函数指针、一个函数、一个lambda、一个std::::function……这里的缺点是(外部)函数变成了模板,因此需要在头中实现。另一方面,由于(外部)函数的客户端代码“看到”回调的调用将提供确切的类型信息,因此可以内联回调的调用。

Example for the version with the template parameter (write & instead of && for pre-C++11):

使用模板参数的版本示例(编写&而不是&用于c++ 11):

template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
    ...
    callback(...);
    ...
}

As you can see in the following table, all of them have their advantages and disadvantages:

如下表所示,它们各有利弊:

+-------------------+--------------+---------------+----------------+
|                   | 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) Workarounds exist to overcome this limitation, for example passing the additional data as further parameters to your (outer) function: myFunction(..., callback, data) will call callback(data). That's the C-style "callback with arguments", which is possible in C++ (and by the way heavily used in the WIN32 API) but should be avoided because we have better options in C++.

(1)解决方案的存在可以克服这个限制,例如将附加的数据作为进一步的参数传递给您的(外部)函数:myFunction(…),回调,数据)将调用回调(数据)。这是C风格的“带参数的回调”,在c++中是可能的(顺便提一下,WIN32 API中使用得很频繁),但是应该避免,因为我们在c++中有更好的选项。

(2) Unless we're talking about a class template, i.e. the class in which you store the function is a template. But that would mean that on the client side the type of the function decides the type of the object which stores the callback, which is almost never an option for actual use cases.

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

(3) For pre-C++11, use boost::function

(3)对于pre- c++ 11,使用boost::::函数

#2


21  

void (*callbackFunc)(int); may be a C style callback function, but it is a horribly unusable one of poor design.

空白(* callbackFunc)(int);可能是一个C风格的回调函数,但它是一个糟糕的无用设计。

A well designed C style callback looks like void (*callbackFunc)(void*, int); -- it has a void* to allow the code that does the callback to maintain state beyond the function. Not doing this forces the caller to store state globally, which is impolite.

设计良好的C风格回调看起来像void (*callbackFunc)(void*, int);——它有一个void*,允许执行回调的代码维护函数之外的状态。不这样做会迫使调用者在全局存储状态,这是不礼貌的。

std::function< int(int) > ends up being slightly more expensive than int(*)(void*, int) invokation in most implementations. It is however harder for some compilers to inline. There are std::function clone implementations that rival function pointer invokation overheads (see 'fastest possible delegates' etc) that may make their way into libraries.

函数< int(int) >在大多数实现中都比int(*)(void*, int)调用稍微贵一些。然而,对于某些编译器来说,内联却是比较困难的。有一些std:::函数克隆实现可以与函数指针调用开销(参见“最快的可能委托”等)相竞争,这些实现可以进入库。

Now, clients of a callback system often need to set up resources and dispose of them when the callback is created and removed, and to be aware of the lifetime of the callback. void(*callback)(void*, int) does not provide this.

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

Sometimes this is available via code structure (the callback has limited lifetime) or through other mechanisms (unregister callbacks and the like).

有时这可以通过代码结构(回调具有有限的生存期)或其他机制(取消注册回调等)来实现。

std::function provides a means for limited lifetime management (the last copy of the object goes away when it is forgotten).

函数为有限的生命周期管理提供了一种方法(当对象被遗忘时,最后一个副本将消失)。

In general, I'd use a std::function unless performance concerns manifest. If they did, I'd first look for structural changes (instead of a per-pixel callback, how about generating a scanline processor based off of the lambda you pass me? which should be enough to reduce function-call overhead to trivial levels.). Then, if it persists, I'd write a delegate based off fastest possible delegates, and see if the performance problem goes away.

通常,我将使用std::函数,除非性能问题出现。如果他们这样做了,我将首先查找结构更改(而不是每个像素的回调,那么基于您传递给我的lambda生成一个扫描线处理器怎么样?)这应该足以将函数调用开销降低到微不足道的级别)。然后,如果它继续存在,我将根据最快的委托编写一个委托,看看性能问题是否消失。

I would mostly only use function pointers for legacy APIs, or for creating C interfaces for communicating between different compilers generated code. I have also used them as internal implementation details when I am implementing jump tables, type erasure, etc: when I am both producing and consuming it, and am not exposing it externally for any client code to use, and function pointers do all I need.

我通常只对遗留api使用函数指针,或者创建用于在不同编译器生成的代码之间通信的C接口。当我实现跳转表、类型擦除等时,我还将它们用作内部实现细节:当我同时生产和使用它,并且不将它公开给任何客户端代码使用时,函数指针完成了我所需要的一切。

Note that you can write wrappers that turn a std::function<int(int)> into a int(void*,int) style callback, assuming there are proper callback lifetime management infrastructure. So as a smoke test for any C-style callback lifetime management system, I'd make sure that wrapping a std::function works reasonably well.

注意,您可以编写包装器,将std::函数 转换为int(void*,int)风格的回调,前提是有合适的回调生命周期管理基础设施。因此,作为对任何c风格回调生命周期管理系统的冒烟测试,我将确保包装std:::函数工作得相当好。 (int)>

#3


16  

Use std::function to store arbitrary callable objects. It allows the user to provide whatever context is needed for the callback; a plain function pointer does not.

函数的作用是:存储任意可调用对象。它允许用户提供回调所需的任何上下文;普通函数指针则不是。

If you do need to use plain function pointers for some reason (perhaps because you want a C-compatible API), then you should add a void * user_context argument so it's at least possible (albeit inconvenient) for it to access state that's not directly passed to the function.

如果出于某种原因需要使用普通函数指针(可能是因为需要c兼容的API),那么应该添加一个void * user_context参数,这样至少可以(尽管不方便)访问没有直接传递给函数的状态。

#4


13  

The only reason to avoid std::function is support of legacy compilers that lack support for this template, which has been introduced in C++11.

避免std::函数的唯一原因是支持不支持这个模板的遗留编译器,c++ 11引入了这个模板。

If supporting pre-C++11 language is not a requirement, using std::function gives your callers more choice in implementing the callback, making it a better option compared to "plain" function pointers. It offers the users of your API more choice, while abstracting out the specifics of their implementation for your code that performs the callback.

如果不需要支持pre-C+ 11语言,那么使用std:::函数可以让调用者在实现回调时有更多的选择,这使它比“普通”函数指针更好。它为您的API提供了更多的选择,同时抽象出执行回调的代码的实现细节。

#5


0  

std::function may bring VMT to the code in some cases, which has some impact on performance.

在某些情况下,函数可以将VMT带入到代码中,这对性能有一定的影响。