我应该在C ++中使用std :: function或函数指针吗?
在C ++中实现一个callback函数时,我还应该使用C样式的函数指针:
void (*callbackFunc)(int);
或者我应该使用std :: function:
std::function< void(int) > callbackFunc;
总之,使用std::function
除非你有理由不。
函数指针具有不能捕获某些上下文的缺点。 例如,你将无法传递一个lambda函数作为一个callback函数来捕获一些上下文variables(但是如果它没有捕获到这个variables的话,它将会起作用)。 因此调用对象的成员variables(即非静态的)也是不可能的,因为需要捕获对象( this
pointer)。 (1)
std::function
(因为C ++ 11)主要是存储一个函数(传递它不需要存储)。 因此,如果你想存储callback例如在一个成员variables,这可能是你最好的select。 但是,如果你不存储它,这是一个很好的“第一select”,虽然它有被称为被引入一些(非常小)的开销的缺点(所以在一个非常严重的性能危机的情况下,这可能是一个问题,但在大多数它不应该)。 这是非常“普遍”的:如果你关心的是一致的,可读的代码,而且不想考虑你所做的每一个select(也就是说要保持简单),对于你传递的每一个函数都要使用std::function
周围。
想想第三个select:如果你要实现一个小函数,然后通过提供的callback函数来报告某些事情,那么可以考虑一个模板参数 ,该参数可以是任何可调用的对象 ,即函数指针,函子,拉姆达,一个std::function
,…这里的缺点是你的(外层)函数变成了一个模板,因此需要在头文件中实现。 另一方面,由于(外部)函数的客户端代码“看到”对callback的调用,所以可以获得确切的types信息,因此可以调用callback函数。
带有模板参数的版本示例(为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)存在克服这种限制的解决方法,例如将附加数据作为其他parameter passing给(外部)函数: myFunction(..., callback, data)
将调用callback(data)
。 这就是C风格的“带参数的callback”,这在C ++中是可能的(通过在WIN32 API中大量使用),但是应该避免,因为我们在C ++中有更好的select。
(2)除非我们正在谈论一个类模板,即你存储函数的类是一个模板。 但是这意味着在客户端,函数的types决定了存储callback的对象的types,这对于实际的用例来说几乎不可能是一个选项。
(3)对于pre-C ++ 11,使用boost::function
void (*callbackFunc)(int);
可能是一个C风格的callback函数,但它是一个糟糕的devise可怕的不可用之一。
devise良好的C风格的callback看起来像void (*callbackFunc)(void*, int);
– 它有一个void*
允许执行callback的代码保持超出函数的状态。 不这样做会迫使主叫方在全球存储状态,这是不礼貌的。
std::function< int(int) >
在大多数实现中比int(*)(void*, int)
调用稍微昂贵一些。 然而,一些编译器难以内联。 有std::function
克隆实现可以与函数指针调用开销(参见'最快可能的委托'等)相媲美,可能会进入库。
现在,callback系统的客户端往往需要在创build和删除callback时设置资源并对其进行处理,并且要知道callback的生命周期。 void(*callback)(void*, int)
不提供这个。
有时这可以通过代码结构(callback具有有限的生命周期)或通过其他机制(取消注册callback等)来实现。
std::function
为有限的生命周期pipe理提供了一种手段(对象的最后一个副本在忘记时就消失了)。
一般来说,我会使用一个std::function
除非性能问题清单。 如果他们这样做了,我会首先寻找结构上的改变(而不是像素callback,那么根据你通过的lambda生成一个扫描线处理器怎么样?这应该足以将函数调用开销减less到微不足道的水平。 )。 那么,如果它仍然存在,我会写一个delegate
基于最快的代表,并看看性能问题是否消失。
我大多只使用传统API的函数指针,或者创build用于在不同编译器生成的代码之间进行通信的C接口。 当我正在执行跳转表,types擦除等时,我也用它们作为内部实现的细节:当我生产和使用它,而不是在外部暴露它的任何客户端代码使用,和函数指针做我所需要的。
请注意,假设有适当的callback生存期pipe理基础结构,可以编写将std::function<int(int)>
转换为int(void*,int)
样式callback的包装器。 所以作为任何C风格的callback生命周期pipe理系统的烟雾testing,我会确保包装一个std::function
工作得很好。
使用std::function
来存储任意的可调用对象。 它允许用户提供callback所需的任何上下文; 一个普通的函数指针不。
如果你出于某种原因需要使用普通函数指针(可能是因为你需要一个C兼容的API),那么你应该添加一个void * user_context
参数,所以它至less可以(尽pipe不方便)访问非直接的状态传递给函数。
避免std::function
的唯一原因是支持遗留编译器,这些编译器不支持C ++ 11中引入的这个模板。
如果支持pre-C ++ 11语言不是std::function
,那么使用std::function
将为您的调用者提供更多的select来实现callback,与“普通”函数指针相比,这是一个更好的select。 它为您的API的用户提供了更多的select,同时抽象出执行callback的代码的具体实现。
在某些情况下, std::function
可能会将VMT带入代码,这对性能有一定的影响。