在虚函数,function_pointer和仿函数之间进行选择

时间:2011-08-27 10:26:13

标签: c++ virtual functor boost-function

我正在编写一个类,其中一个函数的实现取决于用户。目前我将它作为虚函数,用户需要覆盖我的类来提供它的实现。我正在考虑使它成为一个仿函数(boost :: function)/ function_pointer,以便用户可以注册它。 应用程序对性能至关重要,速度比看起来不错的类更重要。改为仿函数会带来一些性能上的好处吗?

在大多数情况下,它将是一个自由函数,因此函数指针应该没问题。但我想在某些情况下可能需要状态,因此它需要成为一个仿函数。

我是否可以通过注册function_ptr或functor并根据某些bool调用适当的函数来获得任何性能优势?类似的东西。

class Myclass
{
   public:
    registerFuncPtr(FuncPtrSignature);
    registerFunctor(boost::function func);
   private:    
   someMethod(someArgs)
   {
    ...
    if(_isFuncPtr)
        _func_ptr(args);
    else
        _functor(args);
    ...
   }
   bool _isFuncPtr;
   FuncptrSignature _func_ptr;
   boost::function _functor;
}

更新

我正在编写一个共享库,客户端会动态链接它。 没有C ++ 0x。 :(

3 个答案:

答案 0 :(得分:6)

这取决于您的特定用例以及用户如何添加其代码。以下是纯粹的理论分析,但您应该构建一个真实的场景并在实际决定之前测试性能。

如果要在编译时添加代码(即您向用户提供代码,他们创建逻辑并将所有内容编译在一起),那么最好的方法可能是提供一个Functor的模板类型参数。

template <typename Functor> void doProcessing( Functor f) {
   f( data ); 
}

优点是编译器可以访问整个代码,这意味着它可以就是否内联代码进行最佳决策,并且可以在内联的情况下进一步优化。缺点是您需要使用每个新逻辑重新编译程序,并且必须使用用户扩展将产品编译在一起,多次不可能。

如果要在编译主产品之后执行扩展,即客户端可以生成自己的扩展并将它们用于编译的可执行文件(想想插件),那么您应该考虑其他选择:< / p>

  • 接受一个函数指针(C路)

  • 提供一个只有该操作的接口(基类)(Java方式)

  • 使用执行类型擦除的仿函数包装器(boost::function / std::function

这些都是纯粹的表现。 C方式的成本是三者中较小的一个,但是在大多数情况下(与每个函数调用额外的间接),与接口选项的差异可以忽略不计,并且它为您提供了在调用对象中保持状态的选项(这将具有在第一个选项中通过全球状态完成。)

第三个选项是最通用的,因为它将 type-erasure 应用于用户 callable ,并允许用户重用他们现有的代码使用函数适配器(如boost::bindstd::bind以及lambdas(如果编译器支持它们)。这是最通用的,并为用户留下最多的选择。但是它有一个相关的成本:类型擦除中有一个虚拟调度加上对实际代码的附加函数调用。

请注意,如果用户必须编写一个函数来使您的界面与其代码相匹配,那么手工制作的适配器的成本很可能是等价的,因此如果他们需要通用性,那么boost::function / { {1}}是要走的路。

从替代品的成本差异来看,它们很可能非常总体上很小,而且一两个操作是否有问题取决于循环的紧密程度以及用户代码的成本是。如果用户代码将需要几百条指令,则可能没有必要使用最通用的解决方案。如果循环每秒运行几千次,那么抓一些指令也不会有什么不同。返回,编写现实场景并进行测试,同时测试时要注意对用户真正重要的事情。抓住几分钟的操作只需几分钟就不值得放弃更高级解决方案的灵活性。

答案 1 :(得分:1)

如果您在演出之后,请使用裸函数指针。如果需要state,请在函数中提供类型为void*的单独状态参数,并让用户注册其状态。没有任何魔法可以使任何C ++构造比这更快,因为函子和虚函数以及所有其余的都只是包含在或多或少类型安全包络中的函数指针。

更重要的是,在谈论优化时,你不应该相信任何东西,除了你的探查者(这包括BTW上面的段落)。

理论上可以从理论上改进性能,但不能内联用户的函数(编译器不能内联尚未编写的代码)。如果您不编译库,但将其作为源代码分发,则可能会因内联而带来一些性能优势。与往常一样,只有剖析器可以说明。

一个函数对象可能胜过函数指针的情况是将它们传递给函数模板。如果模板本身没有内联,那么函数对象很可能在模板实例化中被内联,而函数指针则不是。

答案 2 :(得分:0)

编译器可以轻松地内联Functor调用而不是通过函数指针调用函数所以是的,更喜欢Functor而不是函数指针。