C ++中向量的初始容量

时间:2012-09-04 20:37:56

标签: c++ memory-management stl vector

使用默认构造函数创建的capacity()的{​​{1}}是什么?我知道std::vector为零。我们可以声明默认构造的向量不会调用堆内存分配吗?

这样就可以使用单个分配创建一个具有任意保留的数组,如size()。让我们说出于某种原因,我不想在2345年开始std::vector<int> iv; iv.reserve(2345);

例如,在Linux(g ++ 4.4.5,内核2.6.32 amd64)

size()

打印#include <iostream> #include <vector> int main() { using namespace std; cout << vector<int>().capacity() << "," << vector<int>(10).capacity() << endl; return 0; } 。这是规则,还是STL供应商依赖?

6 个答案:

答案 0 :(得分:54)

标准没有指定容器的初始capacity应该是什么,因此您依赖于实现。一个常见的实现将启动容量为零,但不能保证。另一方面,没有办法改善你的std::vector<int> iv; iv.reserve(2345);策略,所以坚持下去。

答案 1 :(得分:20)

std :: vector的存储实现差异很大,但我遇到的所有实现都是从0开始。

以下代码:

#include <iostream>
#include <vector>

int main()
{
  using namespace std;

  vector<int> normal;
  cout << normal.capacity() << endl;

  for (unsigned int loop = 0; loop != 10; ++loop)
  {
      normal.push_back(1);
      cout << normal.capacity() << endl;
  }

  std::cin.get();
  return 0;
}

提供以下输出:

0
1
2
4
4
8
8
8
8
16
16

根据GCC 5.1和:

0
1
2
3
4
6
6
9
9
9
13

在MSVC 2013下。

答案 2 :(得分:5)

据我了解标准(虽然我实际上并没有命名参考),容器实例和内存分配故意被解耦,这是有充分理由的。因此,您可以对

进行不同的单独调用
  • constructor创建容器本身
  • reserve()预先分配一个适当大的内存块,以容纳至少(!)给定数量的对象

这很有道理。 reserve()存在的唯一权利是让您有机会在生成向量时围绕可能昂贵的重新分配进行编码。为了有用,您必须知道要存储的对象的数量,或者至少需要能够做出有根据的猜测。如果没有这样做,你最好远离reserve(),因为你只需更改浪费内存的重新分配。

所以把它们放在一起:

  • 标准故意 指定一个构造函数,允许您为特定数量的对象预先分配一个内存块(至少比分配更合适)具体的实施,固定的“东西”在引擎盖下。)
  • 分配不应该是隐含的。因此,要预先分配一个块,您需要单独调用reserve(),这不需要在同一个构造位置(当您知道所需的大小可以容纳之后,当然可以/应该更晚)
  • 因此,如果一个向量总是预先分配一个实现定义大小的内存块,这将会影响reserve()的预期工作,不是吗?
  • 如果STL自然无法知道向量的预期目的和预期大小,那么预分配块会有什么好处?如果不适得其反,那将是荒谬的。
  • 正确的解决方案是使用第一个push_back()分配和实施特定的块 - 如果之前尚未明确分配reserve()
  • 在必要的重新分配的情况下,块大小的增加也是特定于实现的。我所知道的向量实现以大小呈指数级增长开始,但会将增量速率限制在一定的最大值,以避免浪费大量内存甚至吹掉它。

只有在不受分配构造函数干扰的情况下,所有这些才能实现全面操作和优势。对于reserve()(和shrink_to_fit())可以按需覆盖的常见方案,您有合理的默认值。所以,即使标准没有明确说明,我也很确定假设一个新构造的矢量没有预分配对所有当前的实现都是一个相当安全的选择。

答案 3 :(得分:3)

作为其他答案的一个小补充,我发现当在Visual Studio的调试条件下运行时,即使容量从零开始,默认构造的向量仍将在堆上分配。

具体来说,如果_ITERATOR_DEBUG_LEVEL!= 0,那么vector将分配一些空间来帮助进行迭代器检查。

https://docs.microsoft.com/en-gb/cpp/standard-library/iterator-debug-level

我刚发现这有点令人讨厌,因为我当时使用的是自定义分配器,并没有预料到额外的分配。

答案 4 :(得分:1)

标准不指定容量的初始值,但是STL容器会自动增长以容纳您放入的数据,前提是您不超过最大大小(使用max_size成员函数来了解)。 对于向量和字符串,只要需要更多空间,就可以通过realloc处理增长。假设您要创建一个保持值为1-1000的向量。如果不使用保留,代码通常会在两者之间产生 以下循环中的2和18次重新分配:

vector<int> v;
for ( int i = 1; i <= 1000; i++) v.push_back(i);

修改代码以使用reserve可能会在循环期间导致0分配:

vector<int> v;
v.reserve(1000);

for ( int i = 1; i <= 1000; i++) v.push_back(i);

粗略地说,矢量和字符串容量每次增长1.5到2倍。

答案 5 :(得分:0)

这是一个古老的问题,这里的所有答案都正确地解释了标准的观点以及使用std::vector::reserve以可移植的方式获得初始容量的方式;

但是,我将解释为什么对于任何STL实现来说,在构造std::vector<T>对象时分配内存都是没有意义的;

  1. std::vector<T>类型不完整;

    在C ++ 17之前,如果在实例化时std::vector<T>的定义仍然未知,则构造T的行为是不确定的。 However, that constraint was relaxed in C++17

    为了有效地为对象分配内存,您需要知道其大小。从C ++ 17开始,客户可能会遇到std::vector<T>类不知道T的大小的情况。具有依赖于类型完整性的内存分配特性是否有意义?

  2. Unwanted Memory allocations

    您将需要很多很多次在软件中对图形进行建模。 (树是图);您最有可能将其建模为:

    class Node {
        ....
        std::vector<Node> children; //or std::vector< *some pointer type* > children;
        ....
     };
    

    现在想一想,想像一下您是否有很多终端节点。如果您的STL实现只是为了预期在children中有对象而分配额外的内存,您将非常生气。

    这只是一个例子,请随时考虑更多...