使用默认构造函数创建的 std::vector
的 capacity()
是什么?我知道 size()
为零。我们可以声明默认构造的向量不调用堆内存分配吗?
这样就可以使用单个分配创建具有任意保留的数组,例如 std::vector<int> iv; iv.reserve(2345);
。假设由于某种原因,我不想在 2345 上启动 size()
。
例如,在 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 供应商?
swap
时,所有迭代器和引用都保持有效(end()
除外)。这意味着内联缓冲区是不可能的。
该标准没有指定容器的初始 capacity
应该是什么,因此您依赖于实现。一个常见的实现将从零开始容量,但不能保证。另一方面,没有办法改进您的 std::vector<int> iv; iv.reserve(2345);
策略,所以坚持下去。
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。
据我了解的标准(尽管我实际上无法命名参考),容器实例化和内存分配已被有意解耦,这是有充分理由的。因此,您有不同的、单独的要求
构造函数来创建容器本身
Reserve() 预先分配一个适当大的内存块以容纳至少(!)给定数量的对象
这很有意义。 reserve()
存在的唯一权利是让您有机会在增长向量时围绕可能昂贵的重新分配进行编码。为了有用,您必须知道要存储的对象数量,或者至少需要能够做出有根据的猜测。如果不这样做,您最好远离 reserve()
,因为您只会更改浪费内存的重新分配。
所以把它们放在一起:
该标准有意不指定允许您为特定数量的对象预分配内存块的构造函数(这至少比分配实现特定的、固定的“东西”更可取)。
分配不应该是隐含的。因此,要预先分配一个块,您需要单独调用 reserve() 并且这不需要在同一个构造位置(当然可以/应该在以后,在您知道容纳所需的大小之后)
因此,如果一个向量总是预先分配一个实现定义大小的内存块,这将挫败reserve()的预期工作,不是吗?
如果 STL 自然无法知道向量的预期用途和预期大小,那么预分配块的优势是什么?这将是相当荒谬的,如果不是适得其反的话。
相反,正确的解决方案是使用第一个 push_back() 分配和实现特定块 - 如果之前没有被 reserve() 明确分配。
在必要的重新分配的情况下,块大小的增加也是特定于实现的。我所知道的向量实现从大小呈指数增长开始,但会将增量速率限制在某个最大值,以避免浪费大量内存甚至破坏它。
只有不受分配构造函数的干扰,所有这些才能充分发挥作用和优势。对于常见场景,您有合理的默认值,可以由 reserve()
(和 shrink_to_fit()
)按需覆盖。因此,即使标准没有明确说明,我很确定假设新构造的向量不预分配对于所有当前实现来说是一个非常安全的选择。
作为其他答案的一个小补充,我发现当使用 Visual Studio 在调试条件下运行时,即使容量从零开始,默认构造的向量仍将在堆上分配。
特别是如果 _ITERATOR_DEBUG_LEVEL != 0 那么向量将分配一些空间来帮助迭代器检查。
https://docs.microsoft.com/en-gb/cpp/standard-library/iterator-debug-level
我只是觉得这有点烦人,因为我当时使用的是自定义分配器并且没有期待额外的分配。
这是一个老问题,这里的所有答案都正确地解释了标准的观点以及您可以通过使用 std::vector::reserve
以可移植方式获得初始容量的方式;
但是,我将解释为什么任何 STL 实现在构造 std::vector<T>
对象时分配内存没有意义;
不完整类型的 std::vector
标准没有指定容量的初始值,但 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 倍。
vector::reserve
与指定初始大小不同。采用初始大小值/副本的向量构造函数初始化n
对象,因此具有线性复杂度。 OTOH,调用reserve 仅意味着复制/移动size()
元素如果 触发了重新分配。在空向量上没有可复制的内容。因此,即使实现为默认构造的向量分配内存,后者也可能是可取的。