【问题标题】:When to use std::function instead of inheritance?何时使用 std::function 而不是继承?
【发布时间】:2013-05-02 09:08:28
【问题描述】:

在某些情况下std::function 可以代替继承。以下两个代码 sn-ps 非常相似(调用函数时的成本大致相同,签名中的用法几乎相同,并且在大多数情况下 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; }
};

为了更清楚地说明这两个sn-ps是如何关联的:两者都可以按如下方式使用:

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-like 对象之间的唯一关系是基于它们的功能。就面向对象的编程而言,它可能被认为不太干净(但就函数式编程而言,当然不是)。

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

【问题讨论】:

  • 也许我只是粗,但我看不出这两个例子有什么关系。
  • IMO 您应该始终使用std::function 或通常的模板方法template&lt;Callable&gt;,因为它们是惯用的。滚动您自己的 Function 类型不适用于 所有 可调用类型。
  • std::function 是 C++ 语言。所以我会选择这个,或者会使用template(类模板和函数模板)。

标签: c++ c++11 boost-function


【解决方案1】:

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

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

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

相反,您必须通过引用(可能通过引用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); 
     }
    

    这也将为您提供足够的灵活性来传递 lambda、函数指针、仿函数,基本上是 any 可调用对象。例如,参见this Q&A on StackOverflow

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

    当您想要存储任何类型的可调用对象以供以后调用时,通常需要采用这种基于std::function 的设计,例如Command design pattern,或者当您有要处理和调用的可调用对象的异构集合时。有关何时使用std::function 而不是模板的讨论,请参阅this Q&A on StackOverflow

  3. 那么什么时候应该使用继承的硬编码设计?好吧,在所有那些 1. 和 2. 不可行的情况下 - 老实说,我现在想不出,但我相信有人可以想出一个极端的案例。请注意,为了使用类似std::function 的习语,C++11 不是必要的,因为 Boost 有一个 boost::function 和一个 boost::bind 实现早于日期(和为 C++11 的 std::functionstd::bind 提供了灵感。


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

【讨论】:

  • +1 表示 std:function 在设计模式中的用处。另请参阅ACCU 2013 presentation
  • @rhalbersma:这是一个非常有趣的演示,谢谢分享!
  • 我更正了你提到的错误;因为开会,没能早点做。在我看来,1. 也有两个缺点:代码膨胀/代码可能需要更长的时间来编译和不太有用的错误消息。但感谢伟大的链接和伟大的答案!
  • @MarkusMayr​​:你说得对,模板往往会导致令人困惑的错误消息,但通过一些练习,大多数情况下它并没有那么糟糕。此外,Concepts Lite(我们也许能够在希望不是太远的未来使用)应该可以解决这个问题。至于代码膨胀,我个人从来没有理由担心,但我可以想象在一些必须最小化代码大小的嵌入式架构上,这可能会产生影响。感谢您带来它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-07
  • 2015-03-13
  • 2015-08-26
  • 1970-01-01
  • 1970-01-01
  • 2010-10-24
  • 1970-01-01
相关资源
最近更新 更多