106

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

这样就可以使用单个分配创建具有任意保留的数组,例如std::vector<int> iv; iv.reserve(2345);. 假设由于某种原因,我不想size()在 2345 上启动。

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

#include <iostream>
#include <vector>

int main()
{
  using namespace std;
  cout << vector<int>().capacity() << "," << vector<int>(10).capacity() << endl;
  return 0;
}

印刷0,10。这是一个规则,还是依赖于 STL 供应商?

4

6 回答 6

78

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

于 2012-09-04T20:44:55.990 回答
51

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;
  }
  
  cin.get();
  return 0;
}

给出以下输出:

0
1
2
4
4
8
8
8
8
16
16

在 GCC 5.1、11.2 - Clang 12.0.1 和:

0
1
2
3
4
6
6
9
9
9
13

根据 MSVC 2013。

于 2016-04-02T08:34:46.657 回答
8

据我了解的标准(尽管我实际上无法命名参考),容器实例化和内存分配已被有意解耦,这是有充分理由的。因此,您有不同的、单独的要求

  • constructor创建容器本身
  • reserve()预先分配一个适当大的内存块以容纳至少(!)给定数量的对象

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

所以把它们放在一起:

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

只有不受分配构造函数的干扰,所有这些才能充分发挥作用和优势。reserve()对于常见场景,您有合理的默认值,可以通过(and shrink_to_fit())按需覆盖。因此,即使标准没有明确说明,我很确定假设新构造的向量不预分配对于所有当前实现来说是一个非常安全的选择。

于 2018-04-19T08:34:59.053 回答
5

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

特别是如果 _ITERATOR_DEBUG_LEVEL != 0 那么向量将分配一些空间来帮助迭代器检查。

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

我只是觉得这有点烦人,因为我当时使用的是自定义分配器并且没有期待额外的分配。

于 2018-03-01T10:44:05.740 回答
4

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

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

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

    在 C++17 之前,std::vector<T>如果在实例化点的定义T仍然未知,则构造 a 是未定义的行为。但是,该约束在 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.

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

于 2019-12-22T00:35:23.717 回答
2

标准没有指定容量的初始值,但 STL 容器会自动增长以容纳尽可能多的数据,前提是您不超过最大大小(使用 max_size 成员函数知道)。对于向量和字符串,当需要更多空间时,增长由 realloc 处理。假设您想创建一个持有值 1-1000 的向量。在不使用保留的情况下,代码通常会在以下循环期间导致 2 到 18 次重新分配:

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

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

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

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

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

于 2018-03-01T11:14:45.433 回答