std::vector::resize() 与 std::vector::reserve() 的区别

102

这篇文章的评论区有一个关于使用std::vector::reserve()std::vector::resize()的线程。

以下是原始代码:

void MyClass::my_method()
{
    my_member.reserve(n_dim);
    for(int k = 0 ; k < n_dim ; k++ )
         my_member[k] = k ;
}

我认为正确的方法来向vector中写入元素是调用std::vector::resize(),而不是std::vector::reserve()

事实上,在VS2010 SP1的debug构建中,以下测试代码会“崩溃”:

#include <vector>

using namespace std;

int main()
{
    vector<int> v;
    v.reserve(10);
    v[5] = 2;

    return 0;
}

我是对的,还是错的?而且,VS2010 SP1是对的,还是错的?


12
解释可能就是“我错了”这么简单 :D - Luchian Grigore
7
我将此标记为“过于局限”,因为@LuchianGrigore很少出错。 - default
1
@Default 将“很少犯错”解读为“快速纠正错误” :) - Luchian Grigore
1
原帖中的代码已更新以正确使用resize(),并且疑问已得到解决。对于版主们:如果认为这个问题“过于局限”,请随意删除它;或者如果您认为它可能会在未来帮助其他人,请保留它。 - Mr.C64
1
这个问题实际上在我将项目从VC6迁移到VS2013时解决了我的疑惑。谢谢:) - pengMiao
显示剩余6条评论
5个回答

143

有两种不同的方法是有原因的:

std::vector::reserve会分配内存但不会调整向量大小,这将使其逻辑大小与之前相同。

std::vector::resize实际上会修改向量的大小,并用对象的默认状态填充任何空间。如果它们是int,则它们都为零。

在reserve之后,在您的情况下,您需要很多push_backs才能写入第5个元素。 如果您不想这样做,则应该使用resize。

关于reserve的一件事:如果您使用push_back添加元素,直到达到您保留的容量,则对数据中的任何现有引用、迭代器或指针都将保持有效。因此,如果我保留了1000个,我的大小是5,那么&vec[4]直到向量具有1000个元素之前仍将保持不变。在那之后,我可以调用push_back()并且它会起作用,但是先前存储的&vec[4]的指针可能不再有效。


1
因此,对于空向量,即vec,在reserve vec [1]之后,vec [1]将以段错误结束。 - hailinzeng
3
vec[1] 将会产生未定义行为。 - CashCow
std::vector::reserve会防止在push_back时偶尔复制整个数组吗? - Post Self
这只针对C++11还是特定的std实现?代码似乎使用reserve和[]访问工作正常?https://godbolt.org/z/MhgFdZ - Steve Bronder
1
@Steve_Corrin 未定义行为是未定义的。它可能看起来可以工作。但这仍然是无效的代码。在容器的 < size() 范围之外索引元素是不允许的。根据语言的定义,它们不存在于那里。如果您的编译器决定不发射核弹而是按照您想要的方式 poke/peek RAM,那只是好运气。或者说是坏运气,我想;理想情况下,我们可以捕捉到所有程序员会做的无效操作,但是要达到这个目标_好运气_吧! - underscore_d

17

这要取决于你想要做什么。 reserve 不会添加任何元素到 vector 中;它只改变了 capacity(),这保证了在添加元素时不会重新分配内存(从而使迭代器失效)。resize 会立即添加元素。如果你想以后再添加元素(使用 insert()push_back()),请使用 reserve。如果你想以后访问元素(使用 [] 或者 at()),请使用 resize。所以你的 MyClass::my_method 可以是以下两种形式之一:

void MyClass::my_method()
{
    my_member.clear();
    my_member.reserve( n_dim );
    for ( int k = 0; k < n_dim; ++ k ) {
        my_member.push_back( k );
    }
}
或者
void MyClass::my_method()
{
    my_member.resize( n_dim );
    for ( int k = 0; k < n_dim; ++ k ) {
        my_member[k] = k;
    }
}

你选择哪一个是一个审美问题,但你引用的代码显然是不正确的。


4
可能需要讨论的是当这两种方法都使用小于当前向量大小的数字进行调用时。
使用比容量小的数字调用reserve()不会影响大小或容量。
使用小于当前容器大小的数字调用resize()将缩小容器到该大小,有效地破坏多余的元素。
总而言之,resize()会释放内存,而reserve()则不会。

调整大小 永远不会 释放内存。当大小变小时,析构函数将被调用,但内存保留不变(容量不会改变)。 - John Gordon

2

你说得对,Luchian 只是写错了,可能因为咖啡因流失太多而没有意识到他的错误。


1

resize 实际上会改变向量中元素的数量,如果 resize 导致向量增长,则会默认构造新项。

vector<int> v;
v.resize(10);
auto size = v.size();

在这种情况下,大小为10。
另一方面,reserve仅请求将内部缓冲区增大到指定大小,但不会更改数组的“大小”,只会更改其缓冲区大小。
vector<int> v;
v.reserve(10);
auto size = v.size();

在这种情况下,大小仍为0。
所以回答你的问题,是的,即使你保留了足够的空间,你仍然使用索引运算符访问未初始化的内存。对于一个int来说,这并不是很糟糕,但在类的向量的情况下,你将访问尚未构造的对象。
编译器设置为调试模式的边界检查显然会被这种行为所困扰,这可能是你遇到崩溃的原因。

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