14

在某些情况下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是基于它们的能力。就面向对象的编程而言,它可能被认为不太干净(但就函数式编程而言,当然不是)。

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

4

1 回答 1

21

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

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

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

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

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

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

    相对于基于模板的设计,使用std::function可能会带来显着的运行时开销,并且对于像您概述的基于硬编码继承的解决方案可能还会带来轻微的开销,但它为您提供了灵活性并且它是一个标准成语。此外,像往常一样,当性能受到关注时,进行测量以支持任何假设 - 你可能会得到令人惊讶的结果。

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

  3. 那么什么时候应该使用继承的硬编码设计呢?好吧,在所有那些 1. 和 2. 不可行的情况下——老实说,我现在想不出任何办法,但我敢肯定有人会想出一个极端的案例。请注意,C++11并不是使用std::function-like 惯用语所必需的,因为 Boost 有一个boost::function和 一个boost::bind实现,该实现早于 C++11std::functionstd::bind.


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

于 2013-05-08T09:08:45.490 回答