8

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

代码示例(仅供参考)

a) 动态绑定

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有点自我记录,因为你有一个清晰的接口,你必须实现(通常包含多个方法),同时B有点无政府主义(这意味着它的灵活性)。

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

编辑

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

4

2 回答 2

6

是否有这些范式的一般结果/比较研究?

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

提速是否显着?

简短的回答:这取决于。

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

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

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

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

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

编译时间呢?

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

对于较大系统中的接口(我主要将 A 用于模块间接口,到目前为止我还没有做过真正的大项目),这两种接口的设计含义是什么?

这实际上取决于您的程序的期望。我virtual每年都少写(还有很多其他的)。在其他方法中,模板变得越来越普遍。老实说,我不明白B“无政府主义”是怎么回事。对我来说,A这有点不合时宜,因为有很多合适的选择。它最终是一种设计选择,可以很好地构建大型系统。一个好的系统将使用语言特性的健康组合。历史证明,这个讨论中没有任何特性是编写一个重要的程序所必需的,但是所有特性都是添加的,因为有人在某些特定用途中看到了更好的替代方案。

概括:

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

底线 一个设计良好的大型现代系统将同时有效地使用许多范式。如果您目前大部分时间都在使用 virtuals,那么您 (imo) 做错了——尤其是当您有时间吸收 c++11 时,这仍然是这种方法。如果速度、性能和/或并行性也是重要的考虑因素,那么模板和 lambdas 应该是您更亲密的朋友。

于 2011-09-19T08:00:12.257 回答
1

哪个更好?这取决于。您已经专注于重叠。更好的是关注方法分歧的地方。您还错过了需要同时使用这两种方法的地方。

模板的最大优势在于它们提供了减少,有时是极大地减少千篇一律的代码的能力。模板的另一个优点是元编程。多亏了 SFINAE,您可以做一些真正奇怪的事情。

模板的一个缺点是语法有点笨拙。没有办法解决这个问题。就是这样。模板的另一个缺点是每个实例化都是一个不同的类,与从同一个模板类实例化的其他类完全无关。有一种解决方法:结合这两种方法。使您的模板类派生自一些非模板基类。当然,现在您已经失去了一些运行时优势。

多态性的最大优点是它是动态的。这可能是一个巨大的、巨大的胜利。不要打折。这种多态性存在性能损失,但如果您想要拥有一组遵循公共接口但不同对象对该接口具有不同实现的对象集合,您将以一种或另一种方式付出代价。

于 2011-09-18T23:30:48.997 回答