为什么C ++标准库没有连续内存的deque版本?

时间:2015-10-16 07:58:15

标签: c++ c++11 data-structures stl

根据我的理解,deque会根据您的需要分配新的常量大小的内存块,而不保证它们是连续的。这样可以确保从任何一侧删除都不会使当前指向deque的任何迭代器无效。

这种内存布局在某些情况下很方便,但它通常比连续存储内存慢。

更快的连续内存的首选容器是矢量,但它不允许你从前面推或弹。

我不明白为什么会这样。

我确信可以实现一个使用连续内存的双端队列,看起来它肯定会优于向量。它的内存布局同样快,并且还可以从前面有效地推送/弹出。

不仅如此,我觉得从设计角度来看也更有意义。连续的双端队列将是它速度的首选,并且当内存布局更好地适应手头的问题时,将使用非连续的双端队列。

如果我遗失某些东西或者看上去非常短视,请告诉我。为什么标准库中没有连续的双端队列?

3 个答案:

答案 0 :(得分:9)

标准库上的容器并不是要涵盖所有可能的用例,它们只是一组有用的数据结构(主要是)非重叠属性(如果将四个关联容器统计为一个单独的族,并且作为另一个家庭的四个无序容器。)

对于连续内存,请使用向量。要进行高效拼接,请使用列表。对于任何一端的有效插入和删除而没有大量的重新分配和改组,请使用deque等。

如果你想要一个不同的数据结构然后编写它,这就是STL设计的要点:遵循相同原则的任何其他容器都将与STL风格的算法完美配合。

你提出的结构比矢量有更多的开销,需要四个指针而不是三个。它可能需要前端容量和后容量成员函数,因此您可以知道在任一端插入是否会重新分配和/或使迭代器无效。当它在一端用完空间时,你必须决定是否重新分配或者只是将一切都移动到另一端的自由空间中。根据使用模式的不同,做出错误的决定可能会很昂贵(例如,重新分配并在两端有更大的增长空间可能会更好,但这可能会导致一端浪费大量空间,如果你洗牌而不是重新分配你可能只是推迟不可避免的事情,并且无论如何都必须重新分配。)

但这些不是无法解决的问题,所以原则上没有什么能阻止你写这样的容器。这并不意味着它必然需要在标准库中。

标准库也没有像Boost.Container的flat_map和stable_vector这样的容器,但您可以在第三方库中找到它们。同样,这种可扩展性是STL设计的重点。

答案 1 :(得分:4)

它已经存在。您可以在矢量的前面插入。

是的,当然,它会使引用和迭代器无效,并且需要复制(或移动)向量的内容,但如果你想要连续的内存,那就必须做出权衡。

deque给你的是保证在任一端插入使对现有元素的引用无效。这是唯一可能的,因为不会使用连续的内存。

答案 2 :(得分:2)

我想,对于这样的容器几乎没有必要,因为如果超出分配的内存,对连续内存的惩罚将是迭代器的失效和重新分配(包括复制/移动元素)。

deque支持以O(1)成本推送任一端,而无效现有迭代器,重新分配,复制或移动元素。这显然被认为比连续记忆更重要。

vector实现了一个可连续内存的可扩展容器,并通过三个数据成员实现,对应beginendend_memory

P-------------E----------C   linear memory
^             ^          ^
begin         end        end_memory

只要end<end_memorypush_back(= ++end),你就可以pop_back(= --end)。最初是end=end_memory=begin+size,因此,如果您从不推送或弹出,则不会浪费任何内存,也不需要重新分配(如果您reserve(),则end_memory=begin+capacity允许您执行某些push_back 1}}没有重新分配)。

为了拥有连续的内存并且能够有效push_front,您需要四个数据成员:

A------------B--------E---------C   // linear memory
^            ^        ^         ^
begin_memory begin    end       end_memory

push_front实施为--begin时。但是,构造新容器时begin没有明显的方法。你可以使用默认值begin_memory=beginend=end_memory=begin+size,但是你会有一个更复杂的reserve

reserve(size_t capacity_to_push_front,
        size_t capacity_to_push_back);

因此,如果您确实需要,请自行实施。