C ++ std :: vector是如何实现的?

时间:2010-01-19 19:51:12

标签: c++ stl

我一直在使用std::vector,最近我问自己这个问题:“std::vector如何实施?”

我有两种选择:

1)链接列表,然后使API感觉像随机访问(即重载operator[])。

2)使用new,例如Foo* temp = new Foo[20]:我相信他们会做这样的事情,但后来又提出了一个问题。他们是否总是分配最大(uint32_t)存储空间来提供随机访问? (这在内存方面效率很低。)

或者还有其他我应该注意的事情吗?

9 个答案:

答案 0 :(得分:42)

它是通过使用底层数组实现的。

使用链表实现std::vector<T>是不可能的,因为标准保证列表中的元素将保存在连续的内存中。

答案 1 :(得分:24)

我相信这是第三种选择。它不能只使用new T[n]因为它实际上必须构造它分配的对象。 E.g

std::vector<Foo> v;
v.reserve(10);

如果你的实现只是结束了new Foo[10]那么你就构建了10个Foo实例。

相反,它使用其分配器来分配和释放原始内存(不构造对象),并根据需要(例如,当您实际使用push_back个对象时)将复制构造的实例放入其保留中的正确内存位置放置新并使用对析构函数的显式调用删除它们(您只需要与placement new结合使用)。 allocator类为我假设vector的实现使用

提供了以下方法
 void construct(pointer p, const_reference val);

  Returns:
    new((void *)p) T(val)

  void destroy(pointer p);

  Returns:
    ((T*)p)->~T()

(“回报”可能应该是“效果”或类似内容。)

有关placement new

的更多信息

答案 2 :(得分:16)

他们使用动态分配的数组,根据需要重新生成。有必要使用类似数组的东西,以便元素在内存中是连续的,这是由标准保证的。

顺便提一下,重新生成数组的一种常用方法是根据需要将大小加倍。这样,如果您要插入n个项目,则只会执行O(log n)个重新生成,并且最多会浪费O(n)个空间。

您可以在SGI(最初构思STL)中为自己阅读一个实现。

答案 3 :(得分:2)

没有一种方法可以实施。不同的实现可以是不同的,只要保留语义并满足要求即可。

在任何给定时间,必须有一个原始数组T来满足邻接的要求。但是,如何分配,增长,缩小和释放它取决于实现者。

您可以自己阅读实现,它就在头文件中。

我可以告诉你,没有实现使用链接列表。它们与标准的要求不一致。

答案 4 :(得分:2)

第23.2.4节,该标准的第1节要求对指向矢量的指针的算法与指向数组的指针相同。

  

存储向量的元素   连续地,意思是如果v是a   矢量,其中T是一些   除了布尔之外的类型,然后它服从   身份&amp; v [n] ==&amp; v [0] + n for   全0&lt; = n&lt; v.size()。

这可以保证存储在阵列中。当然,如果你将数组的大小调整为更大,它可能会在内存中移动。

答案 5 :(得分:2)

一个名为“Vec”的容器的教学(并因此简化)版本将在精彩(介绍性)一书“Accelerated C ++”的第11章中讨论。他们描述的是std :: vector的精简版本,但我认为值得注意的是:

1)他们用数组实现他们的模板类,

2)他们讨论了按照上面提到的分配更多存储空间的技巧(如上所述)的push_back,并在它们用完时回来更多,并且

3)他们使用分配器<T&gt;用于内存管理。在这种情况下,new运算符不够灵活,因为它分配和初始化内存。

但是,我重复一遍,这并不意味着实际的实现就是这么简单。但由于“Accelerated C ++”非常普遍,有兴趣的人可以在相关章节中找到一种可以创建,复制,分配和销毁类似矢量对象的方法。

编辑:在相关的说明中,我刚刚发现了Herb Sutter发表的以下博客文章,其中他评论了Andrew Koenig先前的博客文章,关于是否应该担心矢量元素在内存中是连续的:{ {3}}

答案 6 :(得分:1)

我相信STL使用选项#2(或类似的东西),因为std :: vector&lt;&gt;保证将元素存储在连续的内存中。

如果您正在寻找不需要使用连续内存的内存结构,请查看std :: deque。

答案 7 :(得分:1)

在任何体面的实现中都没有实际的数组(如果有的话,没有默认构造函数就不能使用任何对象),而只是原始内存被分配。它的分配方式通常是每次需要扩展时加倍。

然后,向量使用就地分配,在实际使用每个插槽实际使用​​后,在适当的位置调用类的构造函数。

当有扩展时,它将尝试重新分配到位(但这有点愚蠢,通常不起作用,认为windows 98堆压缩)但通常最终会进行全新的分配并复制。

标准的stl向量总是在一起,但并非所有的实现都是这样的(我知道,已经写了一些)。但是,也许没有一个完全是一个链表。

答案 8 :(得分:0)

根据我在书中阅读的内容以及保留的功能以及向量元素连续的要求,我认为这可能是实现Vector的可能方式。

1)向量的元素是连续的,支持O(1)随机访问,向量应与C数组兼容。这只是暗示没有链表。

2)当您调用reserve时,它会保留额外的内存。但保留会打电话

new T[newSize]

保留更多内存。否则它将调用默认构造函数。正如uncleben解释的那样,每当调用reserve时,向量类只需在其分配器中分配更多未初始化的内存(如果需要),并使用placement new(如果已分配更多内存)将新对象复制到该内存中

3)最初,vector有一些默认容量。在构造向量对象时为其分配未初始化的内存

4)push_back copy将对象构造到第一个可用位置。如果需要,必须以与reserve

类似的方式分配更多内存