何时使用std::函数而不是继承



在某些情况下,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; }
};

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

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

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

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;
}

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

除此之外,这两种解决方案之间还有其他区别吗?当使用哪种解决方案时,是否有任何通用指南,或者这只是个人偏好的问题?是否存在一种解决方案性能优于另一种的情况?

首先进行一个小的更正:注意,这个:

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

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

相反,为了利用多态性,您必须通过引用(可能通过引用const(获得Function派生的对象:

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

这不像函数式编程,所以如果你热衷于函数式编程(就像你看起来的那样(,你可能会因为这一点而想要避免它。


也就是说,以下是我将提供的指导方针:

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

    template<typename F>
    int anotherFunction(F f, int x) { return f(x) + f(x); }
    

    一般来说,当它可以使用时,静态(编译时,基于模板(多态性被认为比动态(运行时,基于继承(多态性更可取,因为:

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

      anotherFunction([] (int x) { return x * 2; }, 42);
      

      以及:

      anotherFunction(myFxn, 42); // myFxn is a regular function
      

      甚至:

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

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

     int anotherFunction(std::function<int (int)> f, int x) 
     { 
         return f(x) + f(x); 
     }
    

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

    对于基于模板的设计,使用std::function可能会带来显著的运行时开销,对于基于硬编码继承的解决方案(如您概述的解决方案(,可能也会带来轻微的开销,但它为您提供了灵活性这是一个标准习惯用法。此外,和往常一样,当性能是一个问题时,进行测量以支持任何假设——你可能会得到令人惊讶的结果。

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

  3. 那么,您应该在什么时候采用使用继承的硬编码设计?好吧,在所有这些情况下,1。和2。是不可行的——老实说,我现在想不出任何可行的方案,但我相信有人会想出一个角落的方案。请注意,为了使用类似std::function的习惯用法,C++11不是必需的,因为Boost有一个boost::functionboost::bind实现,它们早于C++11的std::functionstd::bind


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

最新更新