为什么std :: vector项删除不会降低其容量?

时间:2014-07-12 00:36:36

标签: c++ vector

我知道当我们将项目插入向量时,其容量可能会因非线性因素而增加。在gcc中,它的容量增加了一倍。但我想知道为什么当我从矢量中删除项目时,容量不会减少。我试图找出原因。它似乎' C ++标准没有说任何关于这种减少的说法(要么做或不做)。

理想情况下,当矢量大小在项目删除时达到其容量的1/4时,矢量可以缩小其容量的1/2,以实现恒定的摊销空间分配/解除分配复杂性。

我的问题是为什么C ++标准没有规定容量减少政策?什么是语言设计目标,不指定任何相关的内容?有没有人对此有所了解?

3 个答案:

答案 0 :(得分:4)

  

似乎'C ++标准没有说任何关于这种减少的信息(要么做或不做)

事实并非如此,因为vector::erase的复杂性描述确切地指明了将要执行的操作。

来自§23.3.6.5/ 4 [vector.modifiers]

 iterator erase(const_iterator position);
 iterator erase(const_iterator first, const_iterator last);
     

复杂性: T的析构函数被称为等于被删除元素数的次数,但T的移动赋值运算符称为数字时间等于擦除元素后向量中元素的数量。

这排除了减少容量的实现,因为这意味着重新分配存储并将所有剩余元素移动到新内存。


如果你问为什么标准本身没有指定实现允许在擦除元素时减少容量,那么人们只能猜测原因。

  • 从性能的角度来看,在删除时vector花费时间重新分配和移动元素可能被认为不够重要

  • 由于内存分配失败,减少容量还会增加额外的异常可能性。


您可以通过调用vector::shrink_to_fit来尝试自己减少容量,但要注意此调用是非绑定的,并允许实现忽略它。

减少容量的另一种可能性是将元素移动到临时vector并将swap移回原始元素。

decltype(vec)(std::make_move_iterator(vec.begin()), 
              std::make_move_iterator(vec.end())).swap(vec);

但即使使用第二种方法,也没有什么可以阻止过度分配存储的实现。

答案 1 :(得分:2)

移动所有元素的性能甚至超过了对现有迭代器和元素指针的影响。 erase的行为是:

  

在擦除点或之后使迭代器和引用无效。

如果发生重新分配,则所有迭代器,指针和引用都将变为无效。一般来说,保持迭代器的有效性是一件令人满意的事情。

答案 2 :(得分:0)

随着向量的增长分配额外空间的算法具有恒定的摊销复杂度"由于总复杂度(当通过一系列push_back()操作创建N个元素的向量时为O(N))的概念可以"摊销"通过N个push_back()调用 - 也就是说,总成本除以N.

更具体地说,使用每次分配两倍空间的算法,最坏的情况是如果事先知道向量的确切大小,算法分配的内存几乎是需要分配的4倍。 。最后一次分配仅略小于分配后向量大小的两倍,并且所有先前分配中的一些分配略小于最后一次分配的大小。 分配总数为O(log N),解除分配数(直到该点)仅比分配数少一个。

对于大型向量,如果事先知道其最大大小,则在开始时保留该空间会更有效(一次分配而不是O(log N)分配) 在插入任何数据之前。

如果每次向量大小缩小到当前分配空间的1/4时将容量减少一半 - 也就是说,如果你反向运行分配算法 - 那么你将重新分配(然后解除分配)几乎与向量的最大容量一样多的内存, add 以释放具有最大容量的内存块。对于那些只是想要擦除向量元素直到它们全部消失然后删除向量的应用程序,性能会受到影响。

也就是说,通过重新分配和分配,如果可以的话,最好一次性完成。通过释放你(几乎)总是可以。

更复杂的释放算法的唯一受益者是创建向量的应用程序,然后删除至少3/4,然后保持 继续 部分 内存,同时继续增长新的向量。即使这样,复杂的算法也没有任何好处,除非旧的(但仍然存在的)向量和新向量的最大容量之和太大,以至于应用程序开始遇到虚拟内存的限制。

为什么要惩罚逐步擦除其向量的所有算法,以便在这种特殊情况下获得这种优势?

相关问题