继承与虚函数与通用编程

时间:2011-11-04 12:55:33

标签: c++ stl generic-programming

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

5 个答案:

答案 0 :(得分:15)

他们服务于不同的目的。通用编程(至少在C ++中)是关于编译时多态,以及关于运行时多态性的虚函数。

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

答案 1 :(得分:14)

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

另一方面,模板变为“水平”:模板的每个实例具有相同的形式代码内容,但是两个不同的实例完全是分开的,不相关的部分以“平行”运行并且彼此不看。排序整数数组与排序浮点数组正式相同,但整数数组与浮点数组完全无关。

由于这两个概念完全正交,因此它们的应用也是如此。当然,你可以设想你可以相互替换的情况,但是当习惯性地完成时,模板(通用)编程和继承(多态)编程都是独立的技术,它们都有它们的位置。

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

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

// 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)) { }
  // ...
};

答案 2 :(得分:2)

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

示例:考虑一个离散事件模拟器的事件处理程序:使用纯虚函数实现它非常便宜(在编程工作方面),但如果完全使用模板化类完成,则非常冗长且非常不灵活。

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

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

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

现实情况是,模板会将大部分代码强制转换为标头,这会导致编译时间爆炸。

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

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

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

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

在用Java和C#花了几天之后,我决定我需要'重新思考'我的C ++方法。没有理由C ++程序的编译速度比实现相同函数的底层C慢得多。

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

结果是戏剧性的,编译时间从30秒以上下降到大约一秒钟。后预处理器源代码现在是几千行而不是250,000行。

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