选择 vector::resize() 和 vector::reserve() 之间的选择。

209

我正在为我的vector成员变量预分配一些内存。以下代码是最小的部分:

class A {
  vector<string> t_Names;
public:
  A () : t_Names(1000) {}
};

如果在某个时间点,t_Names.size()等于1000,我想将其大小增加100。当它到达1100时,再次增加100,以此类推。

我的问题是,在这种情况下应该选择vector::resize()还是vector::reserve()?是否存在更好的选择?

编辑:我对t_Names有一个相当精确的估计。我估计它大约为700800。然而,在某些(很少见的)情况下,它可能会增长超过1000


36
你意识到这样做意味着向量增长不再是分摊常数时间,并且你失去了使用std::vector的性能优势之一。 - Blastfurnace
2
相关内容,请参见Dr. Dobbs网站上的C++更易于使用:向量如何增长 - jww
4个回答

350

这两个函数的功能大不相同!

resize() 方法(通过构造函数传递参数也等效于此)将插入或删除适当数量的元素以使向量具有给定大小(它有可选的第二个参数来指定它们的值)。它会影响 size(),迭代将遍历所有这些元素,push_back 将在它们之后插入,并且您可以使用 operator[] 直接访问它们。

reserve() 方法仅分配内存,但不对其进行初始化。它只影响 capacity(),但 size() 将保持不变。对象没有值,因为没有将任何内容添加到向量中。如果然后插入元素,则不会发生重新分配,因为事先已经完成了,但那是唯一的效果。

所以这取决于你想要什么。如果您想要一个包含 1000 个默认项的数组,请使用 resize()。如果您想要一个数组,希望向其中插入 1000 个项并想避免几次分配,请使用 reserve()

编辑: Blastfurnace的评论让我重新阅读问题并意识到,在您的情况下,正确的答案是不要手动预分配。只需按需在末尾插入元素即可。向量将根据需要自动重新分配,并且比提到的手动方式更有效地执行此操作。唯一需要使用reserve()的情况是当您事先有合理精确的总大小估计时很容易获得。 编辑2: 广告问题编辑:如果您有初始估计,请reserve()该估计值。如果发现不足,只需让向量处理即可。

4
@Jan:嗯,它的脆弱程度取决于你为自己维护所需属性制造了多大的困难。像 x.reserve(x.size() + newdata); vector<int>::iterator special_element = get_special_element(x); for (int i = 0; i < newdata; ++i) { if some_function(i, special_element) x.push_back(i); } 这样的代码在保留空间方面相当健壮。我不知道实际上会添加多少元素,但我有一个上限。当然,如果不确定,对于向量,您可以使用索引而不是迭代器,两者之间的区别通常微不足道。 - Steve Jessop
5
您的措辞对于已经知道正确答案的人来说是有意义的,但很容易误导需要提问的人。例如:"resize()...将向向量插入给定数量的元素" - 这只有在第一次使用时才是正确的 - 它通常插入请求数量和现有size()之间的差异。再如,"reserve()方法仅分配内存" - 它可能会或可能不会分配内存,这取决于capacity()是否已足够,它可能还需要移动元素并释放原始内存。如果您希望避免一些分配和复制等操作... - Tony Delroy
31
事实上,在推入(push)之前进行预分配(reserve)非常重要且必须使用。假设你正在编写某种3D模型加载器,该模型具有大约15000个顶点。如果您在加载时尝试push_back每个顶点而没有首先进行预分配,那么它将需要相当长的时间。我个人经历过这种情况,我尝试加载一个具有近100000个顶点的汽车.obj模型,花费了30秒钟。然后我重构了代码,并使用.reserve()进行预分配,现在只需要3秒钟。在代码开头添加.reserve(100000)可以节省27秒钟。 - deniz
2
@deniz 在 100000 规模下这是微不足道的,但在 100-300 规模下却非常不正确,如果没有必要进行保留,那么可能会造成浪费。 - deworde
1
@deniz 实际上,当存储类似顶点这样的东西时,并且您知道将有多少个,则可以使用resize()并通过使用operator[]或行走指针进行分配。 这样可以节省更多时间。 同样不要过度使用reserve。 至少应该非常小心,不要通过以小增量保留来削弱push_back性能。 例如,像vec.reserve(vec.size() + 1)这样的东西,以确保以下的push_back不会抛出异常,对性能是有害的。 - Paul Groke
显示剩余8条评论

46

resize() 不仅分配内存,还会创建与传递给 resize() 的期望大小相同的实例。但是 reserve() 只是分配内存,不创建实例。

std::vector<int> v1;
v1.resize(1000); //allocation + instance creation
cout <<(v1.size() == 1000)<< endl;   //prints 1
cout <<(v1.capacity()==1000)<< endl; //prints 1

std::vector<int> v2;
v2.reserve(1000); //only allocation
cout <<(v2.size() == 1000)<< endl;   //prints 0
cout <<(v2.capacity()==1000)<< endl; //prints 1

输出 (在线演示):

1
1
0
1

如果您不想使用默认创建的对象,则可能不希望使用resize()。 它也会很慢。 另外,如果您对其进行push_back()新元素,则向量的size()将通过分配新内存进一步增加(这也意味着将现有元素移动到新分配的内存空间中)。 如果您在开始时使用reserve()以确保已经分配了足够的内存,则在push_back()时向量的size()会增加,但它不会再次分配新内存,直到它用完为其保留的空间


8
在执行了reserve(N)之后,我们可以安全地使用operator []。正确吗? - iammilind
2
虽然大多数实现会按照 reserve 请求的确切数量进行分配,但规范只要求它至少分配了这么多,因此一些实现可能会将其舍入到某个边界,从而显示比1000更高的容量。 - Jan Hudec
18
不,如果索引大于或等于v.size()。请注意,reserve(N) 不会更改向量的 size() - Nawaz
7
不正确。调用 reSERVE 后,不会添加任何条目,只是获取足够的内存以添加它们。 - Jan Hudec

2

根据您的描述,似乎您想要“保留”向量t_Names的分配存储空间。

请注意,resize会初始化新分配的向量,而reserve仅分配但不构造。因此,“reserve”比“resize”快得多

您可以参考有关resizereserve的差异的文档


1
请参考这里:vectorcapacity (为什么?) - sehe
1
感谢添加链接,sehe。 - dip

2

当您不希望在保留时初始化对象时,可以使用reserve。此外,当您调整大小时,可能更喜欢在逻辑上区分和跟踪其计数与使用计数。因此,在接口中存在行为差异 - 当保留时,向量将表示相同数量的元素,并且在您的情况下调整大小时将增加100个元素。

在这种情况下是否有更好的选择?

这完全取决于您打算对抗默认行为的目的。有些人会选择自定义分配器 - 但我们确实需要更好的了解您在程序中要解决的问题才能给出良好的建议。

顺便说一句,许多向量实现在必须增长时只会将分配的元素数量翻倍 - 您是想最小化峰值分配大小还是为某些无锁程序预留足够的空间或其他目的?


1
“_reserve_” 的意思是当您不希望在保留时初始化对象。正确的表述应该是当您不想让这些对象存在。这不像一个未初始化的数组,它是一个可以被赋值但不能读取的可平凡构造类型;相反,只有内存被保留,但其中没有任何对象存在,因此无法使用 operator[] 或其他方式访问它们。 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接