为什么像 std::vector 这样的标准容器不实现接口?

Aid*_*her 3 c++

我想存储一个队列向量。

队列可能有不同的模板类型,因此我不能将它们存储在同一个向量中。

std::queue<int> aQueue1
std::queue<std::string> aQueue2
std::queue<float> aQueue3
std::vector<std::queue> aVec // Doesn't work because std::queue needs template arguments
Run Code Online (Sandbox Code Playgroud)

我可以在向量中存储指向队列的指针,但是队列并不都实现相同的基类(例如 IQueue)。

std::vector<std::IQueue*> aVec //IQueue does not exist
Run Code Online (Sandbox Code Playgroud)

为什么这不存在?我有哪些选择?

就我而言,用法是我想检查所有队列是否为空。 myqueue.empty()

Yak*_*ont 7

std您在此处使用的库部分-- std::queue-- 来自称为“标准模板库”的库。

在其中,Alexander Stepanov 发现你可以将容器操作与数据操作分离,算法与它们操作的容器分离。同时生成的代码与手工编写的 C 的性能接近。

这太棒了。

您可以使用代码生成实用程序执行类似的操作,但是编译器或调试器无法理解此类生成的代码,至少它是如何连接回原始代码的。STL 让每个 C++ 用户都可以使用具有调整性能和无错误实现的红黑树,而无需考虑太多。

现在,C++ 的原则之一是你不用为你不使用的东西付费。当您向对象添加虚拟接口时,会增加大量开销。

首先,每个实现类的虚函数表必须在运行时存在。其次,增加了RTTI。第三,内联机会消失。第四,实现对象必须携带一个额外的 vtable 指针。第五,分派到方法需要额外的间接层。第六,像迭代器这样的连锁类型变得越来越复杂。

所有这些费用都不是由std::queue<int>. std::queue<int>如果您基于块分配的双数组表编写自己的自定义 C 队列,那么的实现大致就是您所得到的。

我的意思是,如果你想要一个队列,你可能不会写那个。但如果你有很多时间,你可能会。


其他语言选择了不同的路径。像 Java 和 C# 这样的语言几乎所有的对象都通过继承和虚拟表进行堆分配。对象实际上是对对象的垃圾收集引用,并且内存局部性几乎是不可能的。

在大多数任务中,这样做的代价大约是性能下降 2 到 3 倍。他们可以通过使用外部库或非常仔细地编写优化器可以从中删除所有对象开销的代码,在狭窄的情况下解决这个问题。

对于许多人来说,性能降低 2 到 3 倍是可以的。毕竟,对性能的投资是可以替代的,在 Java 和 C# 中,许多任务要容易得多。因此,您可以编写一个在 C#/Java 中蹒跚前行的更快的应用程序,然后将您的工作重点放在关键路径上。当您投入大量精力时,将代码的性能加倍是非常典型的。

C++ 并不是真正的面向对象语言。它具有编写 C++ 的函数式、面向对象和过程式方法。

至于你是怎么解决的?您编写自己的队列抽象。在我的脑海里,我可以想到四种抽象队列的方法。

  1. 您可以编写自己的基本队列类。具有自己的基本队列值。

  2. 你可以写一个std::variants的队列,或者一个std::variant队列。

  3. 你可以写一个std::any.

  4. 您可以对队列操作进行类型擦除并构建您自己的 vtable 实现以允许多态值类型。

哪一个最好?这将取决于您编写代码的能力、问题空间的详细信息以及您正在使用的 C++ 版本。


Ayx*_*xan 6

这根本不是 C++ 的设计方式。如果你想要一个std::queue<std::queue< A few similar types >>,那么你可以使用std::queue<std::queue<std::variant< types >>>. 如果您希望它包含广泛不同类型的开放式列表,您可以使用std::queue<std::queue<std::any>>. 如果您的类型都共享一个公共基类,则可以使用指向基类的(智能)指针队列。如果您有更冒险的想法,您在 C++ 中仍然有更多选择来满足这些需求。我不是建议这样做,但您甚至可以将void*元素存储在队列中并进行自己的类型簿记。

话虽如此,即使这些是可能的,但通常(但可能并非总是如此)您缺少一个更简单的解决方案。

  • 注意:如果设计需要一个可以包含任何数据类型的队列,那么您的设计可能会出现错误。 (5认同)