【问题标题】:Inheritance & virtual functions Vs Generic Programming继承和虚函数与泛型编程
【发布时间】:2011-11-04 12:55:32
【问题描述】:

我需要了解 Inheritance & virtual functions 在 C++ 中是否真的不需要,并且可以使用 Generic programming 实现一切。这来自Alexander Stepanov,我正在观看的讲座是Alexander Stepanov: STL and Its Design Principles

【问题讨论】:

  • 我不明白你的评论。问题是,如果 泛型编程 更强大并且可以实现相同的目标,我们为什么要使用继承。
  • 我认为这是一个很好的问题。对问题和安托万都 +1。
  • @KerrekSB 用膝跳反应来驳回问题太容易了,而不是想太多。这是一个很好的问题,值得思考。泛型编程不是原始的,将其比作 goto 与 for 循环根本不公平。
  • 我还没有看过 Stepanov 的演讲(虽然我会,总是很高兴听到有人聪明),但这里有一个想法:早在 1994 年,模板几乎不存在,每个人都在跳“面向对象”的潮流(也许是“对象东方快车”?),最终在凌晨 5 点做出“世界需要 Java”的致命决定。因此,毫无疑问,当时人们在滥用和误解面向对象的思想,而 Stepanov 的关键认识是许多编程是“通用的”而不是“多态的”(想想排序)。这无疑是一个非常...
  • ...有价值的见解。但仅此一点并不意味着您可以简单地用另一种技术替换一种技术。这只是意味着很多问题可以更好地表达为通用问题而不是多态问题。我们在工具箱中获得了一个非常强大的工具。但这并不意味着我们可以抛弃其他工具。

标签: c++ stl generic-programming


【解决方案1】:

我总是喜欢将模板和继承视为两个正交概念,从字面意义上说:对我来说,继承是“垂直”的,从顶部的基类开始,然后“向下”到越来越多的派生类。每个(公共)派生类在其接口方面的基类:贵宾犬是狗是动物。

另一方面,模板是“水平的”:模板的每个实例都有相同的正式代码内容,但两个不同的实例是完全独立的、不相关的部分以“平行”方式运行并且看不到对方。对整数数组进行排序在形式上与对浮点数数组进行排序相同,但整数数组与浮点数数组完全无关。

由于这两个概念是完全正交的,因此它们的应用也是如此。当然,您可以设计一种可以互相替换的情况,但是如果按照惯用方式完成,模板(泛型)编程和继承(多态)编程都是各自独立的技术。

继承是通过添加细节使抽象概念越来越具体。泛型编程本质上是代码生成

作为我最喜欢的例子,让我提一下这两种技术是如何在一个流行的类型擦除实现中完美结合的:单个处理程序类拥有一个私有的多态指针,指向抽象容器的基类类和具体的派生容器类被确定为模板化类型推导构造函数。我们使用模板代码生成来创建任意系列的派生类:

// internal helper base
class TEBase { /* ... */ };

// internal helper derived TEMPLATE class (unbounded family!)
template <typename T> class TEImpl : public TEBase { /* ... */ }

// single public interface class
class TE
{
  TEBase * impl;
public:
  // "infinitely many" constructors:
  template <typename T> TE(const T & x) : impl(new TEImpl<T>(x)) { }
  // ...
};

【讨论】:

  • “通用编程本质上是代码生成”+1 就是为了这个
【解决方案2】:

它们有不同的用途。泛型编程(至少在 C++ 中)是关于编译时多态性的,而虚函数是关于运行时多态性的。

如果具体类型的选择取决于用户的输入,那么您确实需要运行时多态 - 模板对您没有帮助。

【讨论】:

  • 正如我在回答中试图指出的那样,您可以在您的 runtime polymorphism 案例中使用模板,但是如果/else 分支。
【解决方案3】:

多态性(即动态绑定)对于基于运行时数据的决策至关重要。通用数据结构很棒,但也很有限。

示例:考虑离散事件模拟器的事件处理程序:使用纯虚函数来实现它非常便宜(就编程工作而言),但如果纯粹使用模板类来实现它会很冗长且非常不灵活。

根据经验:如果您发现自己切换(或 if-else-ing)某个输入对象的值,并根据其值执行不同的操作,则可能存在更好的(在可维护性方面)动态绑定解决方案。

前段时间我想过一个类似的问题,我只能梦想给你一个我收到的这么好的答案。也许这有帮助:interface paradigm performance (dynamic binding vs. generic programming)

【讨论】:

    【解决方案4】:

    这似乎是一个非常学术的问题,就像生活中的大多数事情一样,有很多方法可以做事,而对于 C++,你有很多方法可以解决问题。没有必要对事物抱有异或的态度。

    【讨论】:

      【解决方案5】:

      在理想情况下,您可以使用静态多态模板,以便在类型不是由用户输入确定的情况下为您提供最佳性能。

      现实情况是,模板将您的大部分代码强制放入头文件中,这会导致编译时间爆炸式增长。

      我利用静态多态性完成了一些繁重的泛型编程来实现泛型 RPC 库(https://github.com/bytemaster/mace (rpc_static_poly branch))。在这种情况下,协议(JSON-RPC、传输(TCP/UDP/Stream/etc)和类型)在编译时都是已知的,因此没有理由进行 vtable 调度......或者是否存在?

      当我通过预处理器为单个.cpp 运行代码时,它会产生 250,000 行,并且需要 30 多秒来编译单个目标文件。我在 Java 和 C# 中实现了“相同”的功能,并在大约一秒钟内编译完毕。

      您包含的几乎每个 stl 或 boost 标头都会添加成千上万行代码,这些代码必须针对每个对象文件进行处理,其中大部分是多余的。

      编译时间重要吗?在大多数情况下,它们对最终产品的影响比“最大程度地优化 vtable 消除”更重要。原因是每个“错误”都需要一个“尝试修复、编译、测试”周期,如果每个周期需要 30 多秒,开发速度就会慢到爬行(注意谷歌 go 语言的动机)。

      在使用 java 和 C# 几天后,我决定需要“重新思考”我使用 C++ 的方法。没有理由 C++ 程序的编译速度要比实现相同功能的底层 C 慢得多。

      我现在选择运行时多态性,除非分析显示瓶颈在 vtable 调度中。我现在使用模板在处理“void*”或抽象基类的底层对象之上提供“即时”多态性和类型安全接口。通过这种方式,用户不需要从我的“接口”派生出来,并且仍然具有通用编程的“感觉”,但他们可以从快速编译时间中受益。如果性能成为问题,则可以用静态多态性替换通用代码。

      结果是惊人的,编译时间从 30 多秒下降到大约 1 秒。后预处理器源代码现在是几千行而不是 250,000 行。

      在讨论的另一边,我正在为一组相似但略有不同的嵌入式设备开发一个“驱动程序”库。在这种情况下,嵌入式设备几乎没有空间容纳“额外代码”,也没有使用“vtable”调度。对于 C,我们唯一的选择是通过函数指针“分离目标文件”或运行时“多态性”。使用泛型编程和静态多态性,我们能够创建可维护的软件,其运行速度比我们用 C 语言生成的任何东西都快。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-23
        • 2012-08-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多