c++ - 接口(interface)范式性能(动态绑定(bind)与泛型编程)

标签 c++ metaprogramming dynamic-binding

虽然在核心动态绑定(bind)和模板是根本不同的东西,但它们可用于实现相同的功能。

代码示例(仅供引用)

A) 动态绑定(bind)

namespace DB {
  // interface
  class CustomCode {
    public:
      virtual void operator()(char) const = 0;
  };
  class Lib {
    public:
      void feature(CustomCode const& c) {
        c('d');
      }
  };

  // user code
  class MyCode1 : public CustomCode {
    public:
      void operator()(char i) const {
        std::cout << "1: " << i << std::endl;
      }
  };
  class MyCode2 : public CustomCode {
    public:
      void operator()(char i) const {
        std::cout << "2: " << i << std::endl;
      }
  };

  void use() {
    Lib lib;
    lib.feature(MyCode1());
    lib.feature(MyCode2());
  }
}

B) 泛型编程
namespace GP {
  //interface
  template <typename CustomCode> class Lib {
    public:
      void feature(CustomCode const& c) {
        c('g');
      }
  };

  // user code
  class MyCode1 {
    public:
      void operator()(char i) const {
        std::cout << "1: " << i << std::endl;
      }
  };
  class MyCode2 {
    public:
      void operator()(char i) const {
        std::cout << "2: " << i << std::endl;
      }
  };

  void use() {
    Lib<MyCode1> lib;
    lib.feature(MyCode1());
    //lib.feature(MyCode2());  <-- illegal
  }
}



一些想法

虽然这些范式并不完全相同并且各有优缺点( A 更强大一些(参见 MyCode2 ),而 B 对用户来说更灵活)它们都允许实现相同的功能(而上述限制适用)。

无论如何,理论上(TM)A由于虚函数的间接性,运行时有点慢,而 B提供了一些很好的优化机会,因为方法可以内联(当然你没有间接)。
但是,我经常觉得A有点自我记录,因为你有一个清晰的接口(interface),你必须实现(通常由多个方法组成)而 B有点无政府主义(这意味着它的灵活性)。


  • 这些范式是否有任何一般结果/比较研究?
  • 提速是否显着?
  • 编译时间呢?
  • 对于较大系统中的接口(interface)(我主要将 A 用于我的模块间接口(interface),到目前为止我还没有做过真正的大项目),这两种设计有什么设计意义?

  • 编辑

    注意:说“动态绑定(bind)更好,因为它更强大”根本不是答案,因为前提是您有两种方法都适用的情况(否则就没有选择的自由——至少不合理) .

    最佳答案

    Are there any general results / comparative studies of these paradigms?



    据我所知,在文章和出版物中可以找到许多证明的例子。你最喜欢的 c++ 书籍应该提供几个演示;如果您没有此类资源,您可能需要阅读 Modern C++ Design: Generic Programming and Design Patterns Applied - A. Alexandrescu。虽然,没有想到直接回答您的问题的特定资源。同样,结果会因实现和编译器而异——甚至编译器设置也会极大地影响此类测试的结果。 (回答您的每个问题,尽管这不符合对这个特定问题的回答)。

    Is the speed-up significant?



    简短的回答:这取决于。

    在您的示例中,编译器实际上可以使用静态分派(dispatch)甚至内联虚拟函数调用(编译器可以看到足够的信息)。我现在将把响应从一个简单的例子(特别是 OP)转移到更大、更复杂的程序。

    扩展“视情况而定”:是的,加速范围可以从无法测量到巨大。您必须(并且可能已经)意识到在编译时可以通过泛型为编译器提供大量信息。然后它可以使用此信息更准确地优化您的程序。一个很好的例子是使用 std::array对比 std::vector .该 vector 在运行时增加了灵活性,但成本可能相当可观。 vector 需要实现更多的调整大小,动态分配的需求可能会很昂贵。还有其他区别:数组的后备分配不会改变(++优化),元素计数是固定的(++优化),而且 - 在许多情况下不需要通过 new 调用。

    您现在可能会认为这个例子与最初的问题有很大的不同。在许多方面,它真的没有什么不同:随着程序复杂性的增加,编译器对程序的了解越来越多。此信息可以删除程序的几个部分(死代码)并使用 std::array例如,该类型提供的信息足以使编译器可以轻松地说“哦,我看到这个数组的大小是七个元素,我将相应地展开循环”,您将拥有更少的指令并消除错误预测。还有很多,但在数组/vector 的情况下,我看到优化程序的可执行大小在从 vector 转换时减少到 20%。到类似于 array 的界面.同样,代码的执行速度可以提高几倍。事实上,有些表达式完全可以在编译时计算。

    动态调度仍然有它的优点,如果使用得当,使用动态调度还可以提高程序的速度 - 您真正需要学习的内容归结为决定何时偏向于另一个。类似于具有许多变量的巨大函数无法非常有效地优化(真实程序中所有模板扩展的结果),虚函数调用实际上在许多情况下可以是一种更快、更清晰的方法。因此,它们是两个独立的功能,您需要进行一些练习才能确定什么是正确的(而且许多程序员没有花时间充分了解这一点)。

    总之,它们应该被视为单独的特性,适用于不同的场景。这些应该(恕我直言)的实际重叠比它们在现实世界中的实际重叠要少得多。

    What about compilation time?



    使用模板,开发过程中的编译和链接时间可能会相当长。每次标题/模板更改时,您都需要对所有依赖项进行编译——这对于支持动态调度来说通常是一个重大的好处。如果您提前计划并适本地构建,您当然可以减少这种情况 - 了解如何使用模板来掌握更困难的主题。使用模板,您不仅会增加大型构建的频率,还会增加大型构建的时间和复杂性。 (更多注释如下)

    What are the design implications of either for interfaces in larger systems (I mainly used A for my inter-module interfaces and I haven't done really really big projects so far)?



    这实际上取决于您的程序的期望。我写 virtual每年都在减少(还有很多其他的)。在其他方法中,模板正变得越来越普遍。老实说,我不明白如何B是“无政府主义的”。对我来说,A有点不合时宜,因为有很多合适的替代品。它最终是一种设计选择,可以在构建大型系统时考虑很多因素。一个好的系统会使用语言特性的健康组合。历史证明本讨论中的任何功能都不是编写非平凡程序所必需的,但添加所有功能是因为有人在某些特定用途中看到了更好的替代方案。您还应该期望 lambda 在某些(并非所有)团队/代码库中超过 50% 的当前用途中取代虚拟。

    概括:
  • 如果使用得当,模板的执行速度会明显加快。
  • 两者都可以产生更大的可执行文件。如果使用正确并且可执行文件的大小很重要,那么作者将使用多种方法来减少可执行文件的大小,同时提供漂亮的可用界面。
  • 模板可以变得非常复杂。学习爬行和解释错误消息需要时间。
  • 模板将几个错误插入编译域。就个人而言,我更喜欢编译错误而不是运行时错误。
  • 通过 virtuals 减少编译时间通常很简单(virtuals 属于 .cpp)。如果您的程序很大,那么经常更改的庞大模板系统可以快速发送您的重建时间和计数,因为会有很多模块间的可见性和依赖性。
  • 可以使用较少编译文件的延迟和/或选择性实例化来减少编译时间。
  • 在较大的系统中,您必须更加考虑为您的团队/客户强制进行重要的重新编译。使用虚拟是一种减少这种情况的方法。同样,cpp 中将定义更高百分比的方法。另一种选择当然是您可以隐藏更多的实现或向客户端提供使用您的接口(interface)的更具表现力的方式。
  • 在更大的系统中,模板、lambdas/functors 等实际上可以用来显着减少耦合和依赖。
  • virtuals 增加了依赖性,通常变得难以维护,膨胀的接口(interface),并成为结构上笨拙的野兽。以模板为中心的库倾向于颠倒这种优先级。
  • 所有方法都可能用于错误的原因。

  • 底线
    一个大型的、设计良好的现代系统将同时有效地使用许多范式。如果您目前大部分时间都使用 virtuals,那么您 (imo) 就做错了——尤其是如果您有时间吸收 c++11 后这仍然是方法。如果速度、性能和/或并行性也是重要的问题,那么模板和 lambdas 值得成为您的亲密 friend 。

    关于c++ - 接口(interface)范式性能(动态绑定(bind)与泛型编程),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7464889/

    相关文章:

    java - 为什么需要动态绑定(bind)?

    java - Java中是如何实现动态绑定(bind)的?

    C++克隆并创建以充当虚拟构造函数?

    c++ - 未解析的模板参数

    c++ - boost::asio::local::stream_protocol::iostream 不起作用?

    c++ - 如何使用返回第 n 个元素的方法创建元组

    methods - 向元类添加方法

    c++ - 在 CGAL 中与多边形相交时的前提条件异常

    c++ - 从传递给构造函数的参数类型推导可变成员类型

    模块中 Julia 宏中表达式和变量的范围