为什么标准库容器没有共同的基础?

时间:2012-07-13 09:28:40

标签: c++ stl c++-standard-library

只是出于兴趣...

如果我要设计一个容器库,我肯定会从一个公共基类派生它们,它们会有size()insert()之类的方法(可能是抽象的)声明。

标准库容器是否有充分的理由不能像那样实现?

5 个答案:

答案 0 :(得分:13)

在C ++中,继承用于运行时多态(读取:运行时接口重用)。它带有在运行时通过vtable重定向的开销。

为了使多个容器类具有相同的接口(以便API可预测且算法可以做出假设),不需要继承。只要给他们相同的方法,你就完成了。 C ++中的容器(和算法)是作为模板实现的,这意味着你得到了编译时的多态性。

这可以避免任何运行时开销,并且符合C ++的口头禅"只需支付你需要的费用"。

答案 1 :(得分:4)

Java集合(您可能已经考虑过)在AbstractCollection中实现了许多常用方法,这些方法位于具体类中实现的size()iterator()方法之上。那么IIRC在AbstractList中有更多这样的方法,依此类推。有些子类会覆盖大多数这些方法,有些子类只能实现所需的抽象方法。一些方法真正在共享接口的大多数或所有集合中共同实现。例如,在我的痛苦经历中,给你一个不可修改的视图的是一个要写的总PITA。

C ++容器对所有容器都有一些共同的成员函数,几乎所有容器都不能以相同的方式实现所有容器[*]。如果存在可以使用迭代器(如find)执行的常见算法,则它们是<algorithm>中的自由函数,远离任何继承都可以查看的函数。

还有所有序列,所有关联数组等共有的成员函数。但对于每个概念,在不同的具体数据结构的实现之间没有太多共同点。

因此最终归结为设计哲学的问题。 Java和C ++都有一些关于容器的代码重用。在C ++中,此代码重用来自<algorithm>中的函数模板。在Java中,其中一些来自继承。

主要的实际差异可能是在Java中,您的函数可以接受java.util.Collection,而不知道它是什么类型的集合。在C ++中,函数模板可以接受任何容器,但非模板函数不能。这会影响用户的编码风格,并且也会受其影响 - Java程序员比C ++程序员更有可能获得运行时多态性。

从实现者的角度来看,如果您正在编写C ++容器库,那么如果您认为有助于重用代码,则无法阻止您在std中的不同容器之间共享私有基类

[*]现在C ++ 11中保证size() O(1)empty()可能是共同实现的。在C ++ 03中size()仅“应该”为O(1),并且有std::list的实现利用此功能在splice中实现其中一个版本{ {1}}而不是更新大小所需的O(1)

答案 2 :(得分:1)

没有充分的理由介绍这样的基类。谁会从中受益?它会在哪里有用?

需要“通用”接口的算法使用迭代器。没有将元素插入不同容器的常用方法。实际上,您甚至无法将元素插入某些容器。

答案 3 :(得分:1)

如果您可以编写独立于您正在使用的容器的代码,那将是有意义的。但事实并非如此。我建议你阅读本章“有效STL ”中的“第2项:注意与容器无关的代码的错觉

答案 4 :(得分:1)

从另一个角度来看:现在,如果您以通用的方式使用它们,编译器将拥有它可以拥有的所有信息,从而实现彻底的优化。感谢模板实现。

现在,如果例如list和vector都会实现像push_backable这样的抽象OO接口,并且你会在你的代码中使用抽象指针(push_backable *) - &gt; push_back(...),那么编译器会丢失一个大量的信息,从而优化的机会。

这些是可能出现在内部循环中的典型操作,您真的希望对它们进行最大可能的优化。另见Frerich Raabe的答案。