2

我正在编写一个类,其中一个函数的实现取决于用户。目前我把它作为一个虚函数,用户需要重写我的类来提供它的实现。我正在考虑将其设为 functor(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。:(

4

3 回答 3

6

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

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

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

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

如果要在编译您的主要产品之后执行扩展,即客户端可以生成自己的扩展并将它们用于编译的可执行文件(想想插件),那么您应该考虑替代方案:

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

  • 仅提供具有该操作的接口(基类)(Java方式)

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

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

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

请注意,如果用户必须编写一个函数来使您的界面与他们的代码相适应,那么手动制作的适配器的成本很可能是相当的,所以如果他们需要通用性,那么boost::function/std::function是要走的路。

至于替代方案的成本差异,它们很可能总体上非常小,并且抓一两个操作是否重要将取决于循环的紧密程度和用户代码的成本。如果用户代码需要几百条指令,那么不使用最通用的解决方案可能没有意义。如果循环每秒运行几千次,那么抓几条指令也不会产生任何影响。回过头来,编写一个真实的场景并进行测试,并在测试时了解对用户真正重要的内容。花费几分钟的时间来完成几秒钟的操作是不值得失去更高级别解决方案的灵活性的。

于 2011-08-27T11:12:33.763 回答
1

如果您追求性能,请使用裸函数指针。如果您需要状态,void*请在您的函数中提供一个单独的状态参数类型,并让用户注册他们的状态。没有任何魔法可以使任何 C++ 构造比这更快,因为函子和虚函数以及所有其他的都只是包装在或多或少类型安全的信封中的函数指针。

更重要的是,在谈论优化时,除了你的分析器(这包括上面的段落)之外,你不应该相信任何东西。

内联理论上可以提高性能,但用户的函数不能内联(编译器不能内联尚未编写的代码)。如果您不编译您的库,而是将其作为源代码分发,则可能会由于内联而获得一些性能优势。与往常一样,只有分析器才能判断。

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

于 2011-08-27T11:49:42.750 回答
0

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

于 2011-08-27T10:29:00.247 回答