何时使用std :: function而不是继承?

Mar*_*ayr 12 c++ boost-function c++11

在某些情况下std::function可以取代继承.以下两个代码片段非常相似(调用函数时的成本大致相同,签名中的用法几乎相同,在大多数情况下,std :: function也不需要我们制作额外的副本A):

struct Function
{
    virtual int operator()( int ) const =0;
};
struct A
    : public Function
{
    int operator()( int x ) const override { return x; }
};
Run Code Online (Sandbox Code Playgroud)

使用std::function,我们可以重写为

using Function = std::function<int (int)>;
struct A
{
    int operator()( int x ) const { return x; }
};
Run Code Online (Sandbox Code Playgroud)

为了更清楚,两个片段是如何相关的:它们都可以通过以下方式使用:

int anotherFunction( Function const& f, int x ) { return f( x ) + f( x ); }

int main( int argc, char **argv )
{
    A f;
    anotherFunction( f, 5 );
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

后一种方法更灵活,因为我们不必从一些公共基类派生我们的类.类似Function对象之间的唯一关系是基于它们的能力.在面向对象的编程方面,它可能被认为不太干净(当然不是在函数式编程方面).

除此之外,两种解决方案之间还有其他差异吗?是否有任何一般性指导原则何时使用哪种解决方案或仅仅是个人偏好?是否存在一种解决方案胜过另一种解决方案的情况?

And*_*owl 20

先做一个小修正:注意,这个:

int anotherFunction( Function f, int x ) { return f( x ) + f( x ); }
Run Code Online (Sandbox Code Playgroud)

不会使用基于继承的解决方案进行编译,因为Function它是由值采用的,而且是抽象的.如果它不是抽象的,另一方面,你会得到切片,这不是你想要的.

相反,您必须Function通过引用(可能通过引用const)获取您的派生对象,以便利用多态性:

int anotherFunction( Function const& f, int x ) { return f( x ) + f( x ); }
Run Code Online (Sandbox Code Playgroud)

这不是很像功能,所以如果你热衷于函数式编程(就像你看起来那样),你可能只想因此而避免它.


也就是说,这是我提供的指导方针:

  1. 如果可以,请使用模板:

    template<typename F>
    int anotherFunction(F f, int x) { return f(x) + f(x); }
    
    Run Code Online (Sandbox Code Playgroud)

    通常,当它可以被使用时,静态(编译时,基于模板)多态被认为优于动态(运行时,基于继承)多态,因为:

    • 卓越的灵活性:您不必更改类型的定义,并使它们从公共基类派生,以便一般使用它们.例如,这允许您编写:

      anotherFunction([] (int x) { return x * 2; }, 42);
      
      Run Code Online (Sandbox Code Playgroud)

      以及:

      anotherFunction(myFxn, 42); // myFxn is a regular function
      
      Run Code Online (Sandbox Code Playgroud)

      甚至:

      anotherFunction(my_functor(), 42); // my_functor is a class
      
      Run Code Online (Sandbox Code Playgroud)
    • 卓越的性能:由于您不是通过虚拟表进行调用,并且编译器知道如何解析函数调用,因此它可以内联调用以提供更高的性能(如果它认为这是合理的).

  2. 如果您不能使用模板,因为要在运行时确定要调用的函数,请使用std::function:

     int anotherFunction(std::function<int (int)> f, int x) 
     { 
         return f(x) + f(x); 
     }
    
    Run Code Online (Sandbox Code Playgroud)

    这也将为您提供足够的灵活性来传递lambdas,函数指针,仿函数,基本上任何可调用对象.例如,请参阅StackOverflow上的此问答.

    使用std::function可能会对基于模板的设计带来显着的运行时间开销,并且对于基于硬编码继承的解决方案(如您所概述的解决方案)而言可能也会带来轻微的小开销,但它为您提供了灵活性并且它是标准的成语.此外,一如既往需要考虑性能,请进行测量以备份任何假设 - 您可能会得到令人惊讶的结果.

    std::function当您想要存储任何类型的可调用对象以供以后调用时,通常需要采用这种基于设计的设计,例如在Command设计模式的情况下,或者当您有可处理的可调用对象的异构集合时,一般地援引.有关何时使用std::function而不是模板的讨论,请参阅StackOverflow上的此问答.

  3. 那么什么时候应该使用继承的硬编码设计呢?好吧,在所有那些1.和2.不可行的情况下 - 老实说,我现在想不出任何问题,但我确信有人可以提出一个角落案例.请注意,为了使用类似的习惯用语,C++ 11 不是必需的std::function,因为Boost有一个boost::function和一个boost::bind实现,它早于(并作为灵感)C++ 11 std::functionstd::bind.


TL; DR:使用模板或std::function.

  • +1在设计模式中提到`std:function`的用处.另见[ACCU 2013年演示文稿](http://accu.org/content/conf2013/Tobias_Darm_Effective_GoF_Patterns.pdf) (4认同)