C++ 容器常见问题:emplace_back、push_back、reserve、resize 的区别

C++ vector 常见接口:push_back、emplace_back、reserve、resize

一、前言

在阅读 C++ 代码时,经常会看到下面几个 std::vector 相关接口:

push_back
emplace_back
reserve
resize

它们看起来都和“往容器里放东西”有关,但实际含义并不相同。

本文主要整理几个问题:

push_back 和 emplace_back 有什么区别?
reserve 和 resize 有什么区别?
什么时候该用哪个?
常见错误有哪些?

本文以 std::vector 为例进行说明。


二、push_back 和 emplace_back

2.1 示例结构体

先定义一个简单的结构体:

struct Point {
    double x;
    double y;
    double z;

    Point(double x, double y, double z)
        : x(x), y(y), z(z) {}
};

定义一个 vector

std::vector<Point> points;

后面用这个 points 来说明 push_backemplace_back 的区别。


2.2 push_back:把已有对象放进容器

push_back 的含义是:把一个已经构造好的对象添加到容器末尾

例如:

Point p(1.0, 2.0, 3.0);
points.push_back(p);

这段代码可以理解为:

先在外部创建 Point 对象 p
再把 p 拷贝到 vector 中

也可以直接传入一个临时对象:

points.push_back(Point(1.0, 2.0, 3.0));

这个过程可以理解为:

先构造一个临时 Point 对象
再把这个临时对象移动到 vector 中
最后销毁临时对象

所以 push_back 更强调的是:把一个现成的对象放进容器


2.3 emplace_back:在容器内部直接构造对象

emplace_back 的含义是:直接在容器末尾构造对象

例如:

points.emplace_back(1.0, 2.0, 3.0);

这行代码会把参数:

1.0, 2.0, 3.0

直接传给 Point 的构造函数:

Point(double x, double y, double z)

也就是说,它会直接在 vector 内部的内存空间中构造一个 Point 对象。

可以简单理解为:

emplace_back 不需要你提前构造 Point 对象
它会用你传入的参数,在容器内部直接构造对象

2.4 两者的核心区别

push_backemplace_back 的核心区别如下:

push_back:先构造对象,再放入容器
emplace_back:直接在容器内部构造对象

例如:

points.push_back(Point(1.0, 2.0, 3.0));

大致过程是:

构造临时 Point 对象
移动到 vector 中
销毁临时对象

而:

points.emplace_back(1.0, 2.0, 3.0);

大致过程是:

直接在 vector 内部构造 Point 对象

所以在某些情况下,emplace_back 可以减少一次临时对象构造或移动。


2.5 emplace_back 并不总是更好

虽然 emplace_back 有时可以减少临时对象,但并不是任何场景都应该无脑使用 emplace_back

例如已经有一个对象:

Point p(1.0, 2.0, 3.0);

下面两种写法差别并不大:

points.push_back(p);
points.emplace_back(p);

因为此时传入的已经是一个 Point 对象,而不是构造 Point 所需的参数。

如果语义是“把已有对象放进容器”,使用 push_back 反而更直观:

points.push_back(p);

如果想把已有对象移动进去,可以写:

points.push_back(std::move(p));

或者:

points.emplace_back(std::move(p));

但从可读性上看,已有对象更推荐使用 push_back


2.6 如何选择 push_back 和 emplace_back

可以按下面的原则选择:

已有对象:使用 push_back 更直观
直接构造对象:使用 emplace_back 更合适
需要转移资源:配合 std::move

例如:

std::vector<Point> points;

Point p(1.0, 2.0, 3.0);

// 已经有对象,放入容器
points.push_back(p);

// 直接在容器内部构造对象
points.emplace_back(4.0, 5.0, 6.0);

// 移动已有对象
points.push_back(std::move(p));

简单记忆:

push_back(obj):把对象放进去
emplace_back(args...):用参数直接在容器里构造对象

三、reserve 和 resize

3.1 先理解 size 和 capacity

在理解 reserveresize 之前,需要先区分 vector 的两个概念:

size:当前实际元素个数
capacity:当前已分配空间最多能容纳的元素个数

例如:

std::vector<int> v;

刚创建时通常是:

v.size()     == 0
v.capacity() == 0

当然,capacity 的具体值由标准库实现决定,但可以先这样理解。


3.2 reserve:只预留空间,不创建元素

reserve(n) 的含义是:提前分配能够容纳 n 个元素的空间,但不创建元素

例如:

std::vector<int> v;
v.reserve(10);

执行后:

v.size()     == 0
v.capacity() >= 10

注意:size 仍然是 0

也就是说,虽然 vector 已经预留了可以容纳 10 个元素的空间,但容器里面还没有真正的元素。

因此下面这种写法是错误的:

std::vector<int> v;
v.reserve(10);

v[0] = 123; // 错误

原因是:

reserve 只是预留内存空间
并没有创建第 0 个元素

正确写法应该是继续使用 push_backemplace_back 添加元素:

std::vector<int> v;
v.reserve(10);

v.push_back(1);
v.push_back(2);
v.push_back(3);

此时:

v.size()     == 3
v.capacity() >= 10

reserve 的主要作用是优化性能,减少 vector 频繁扩容。


3.3 resize:改变元素数量

resize(n) 的含义是:把 vector 的元素数量改成 n 个

例如:

std::vector<int> v;
v.resize(10);

执行后:

v.size()     == 10
v.capacity() >= 10

这时 vector 里面真的有 10 个元素。

对于 int 类型来说,这些元素会被初始化为 0

std::vector<int> v;
v.resize(3);

// v 中现在有 3 个元素:0, 0, 0

所以可以直接使用下标访问:

v[0] = 123;
v[1] = 456;
v[2] = 789;

如果 resize 的大小比原来的 size 更小,多余的元素会被删除:

std::vector<int> v = {1, 2, 3, 4, 5};

v.resize(3);

// v 现在是:1, 2, 3

所以 resize 影响的是容器中真实存在的元素数量。


3.4 reserve 和 resize 的核心区别

reserveresize 的核心区别如下:

接口 是否改变 size 是否改变 capacity 是否创建元素 是否可以直接用下标访问新位置
reserve(n) 可能改变
resize(n) 可能改变

举例:

std::vector<int> a;
a.reserve(10);

结果是:

a.size()     == 0
a.capacity() >= 10

不能这样访问:

a[0] = 1; // 错误

因为 a 中还没有第 0 个元素。

再看:

std::vector<int> b;
b.resize(10);

结果是:

b.size()     == 10
b.capacity() >= 10

可以这样访问:

b[0] = 1; // 正确

因为 b 中已经真实存在 10 个元素。


3.5 常见错误:reserve 后直接用下标访问

一个常见错误是:

std::vector<int> v;
v.reserve(10);

for (int i = 0; i < 10; ++i) {
    v[i] = i;   // 错误
}

这段代码的问题在于:

v.reserve(10);

只是预留了空间,并没有创建 10 个元素。

虽然底层内存可能已经分配好了,但从 vector 的语义上看,v.size() 仍然是 0。因此 v[0]v[1] 等元素都不存在。

正确写法之一是使用 push_back

std::vector<int> v;
v.reserve(10);

for (int i = 0; i < 10; ++i) {
    v.push_back(i);
}

或者使用 emplace_back

std::vector<int> v;
v.reserve(10);

for (int i = 0; i < 10; ++i) {
    v.emplace_back(i);
}

如果确实需要通过下标访问,可以使用 resize

std::vector<int> v;
v.resize(10);

for (int i = 0; i < 10; ++i) {
    v[i] = i;
}

这时 v 中已经真实存在 10 个元素,所以可以通过下标访问。


3.6 如何选择 reserve 和 resize

可以按下面的原则选择:

后续要 push_back / emplace_back:使用 reserve
后续要通过下标访问:使用 resize

例如,如果是不断追加元素:

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

for (int i = 0; i < 1000; ++i) {
    v.push_back(i);
}

适合使用 reserve

如果是先创建固定数量元素,然后通过下标赋值:

std::vector<int> v;
v.resize(1000);

for (int i = 0; i < 1000; ++i) {
    v[i] = i;
}

适合使用 resize

简单记忆:

reserve:我要预留空间,之后慢慢添加
resize:我要现在就有这么多个元素

四、结合点云代码理解

4.1 vector 存储指针的情况

在一些点云相关代码中,可能会看到类似这样的容器:

std::vector<Generic3dPoint*> points;

这里的 vector 存储的不是点对象本身,而是指向点对象的指针。

也就是说,points 中每个元素的类型是:

Generic3dPoint*

4.2 使用 reserve 的含义

如果代码中写:

points.reserve(n);

它的含义是:

提前给 vector 分配能容纳 n 个指针的空间
但 points 当前还没有 n 个元素

此时:

points.size()     == 0
points.capacity() >= n

所以不能直接写:

points[0] = pointPtr; // 错误

因为第 0 个元素还不存在。

后面通常需要配合:

points.push_back(pointPtr);

或者:

points.emplace_back(pointPtr);

完整示例:

std::vector<Generic3dPoint*> points;
points.reserve(n);

for (int i = 0; i < n; ++i) {
    Generic3dPoint* pointPtr = getPoint(i);
    points.push_back(pointPtr);
}

这里 reserve(n) 的作用只是减少后续 push_back 时的扩容次数。


4.3 使用 resize 的含义

如果代码中写:

points.resize(n);

它的含义是:

points 中现在真的有 n 个指针元素

此时:

points.size()     == n
points.capacity() >= n

所以可以通过下标访问:

points[0] = pointPtr;

更安全的写法通常是:

points.resize(n, nullptr);

意思是:

创建 n 个元素,每个元素初始值都是 nullptr

例如:

std::vector<Generic3dPoint*> points;
points.resize(n, nullptr);

for (int i = 0; i < n; ++i) {
    points[i] = getPoint(i);
}

这种写法适合已经知道最终元素数量,并且后面要通过下标直接填充的场景。


五、常用搭配

5.1 reserve + emplace_back

比较常见的高效写法是:

std::vector<Point> points;
points.reserve(10000);

for (int i = 0; i < 10000; ++i) {
    points.emplace_back(i, i + 1, i + 2);
}

这段代码的含义是:

先预留能容纳 10000 个 Point 的空间
然后不断在 vector 内部直接构造 Point 对象

适合场景:

提前知道大概数量
需要不断追加元素
希望减少 vector 扩容次数
对象可以直接通过构造参数创建

这种写法的优点是:

reserve 减少扩容次数
emplace_back 减少临时对象构造或移动

5.2 resize + 下标赋值

另一种常见写法是:

std::vector<int> values;
values.resize(10);

for (int i = 0; i < 10; ++i) {
    values[i] = i;
}

这段代码的含义是:

先创建 10 个元素
然后通过下标逐个赋值

适合场景:

需要提前创建固定数量元素
后续通过下标直接写入
元素数量一开始就确定

对于对象类型,也可以这样使用:

std::vector<Point> points;
points.resize(100);

for (int i = 0; i < 100; ++i) {
    points[i] = Point(i, i + 1, i + 2);
}

不过这里需要注意:

resize 会真的构造元素
如果对象构造成本较高,可能会有额外开销

这种情况下,如果只是不断追加对象,通常 reserve + emplace_back 更合适。


六、总结

6.1 四个接口一句话总结

接口 含义
push_back(obj) 把已有对象加入容器
emplace_back(args...) 用参数在容器内部直接构造对象
reserve(n) 预留空间,不创建元素
resize(n) 改变元素数量,会创建或删除元素

6.2 选择原则

可以按下面的规则记忆:

已有对象:push_back
直接构造对象:emplace_back

提前预留空间:reserve
需要真实元素数量:resize

reserve 后不能直接用下标写
resize 后可以直接用下标写

更具体一点:

1. 如果已经有一个对象,要放进 vector,使用 push_back。

2. 如果想直接用构造参数在 vector 里创建对象,使用 emplace_back。

3. 如果知道后面要插入很多元素,但现在还不需要创建它们,使用 reserve。

4. 如果现在就需要 vector 中存在 n 个元素,使用 resize。

5. reserve 只影响 capacity,不影响 size。

6. resize 会影响 size,也可能影响 capacity。

7. reserve 后不能访问 v[0],因为元素还不存在。

8. resize 后可以访问 v[0],因为元素已经存在。

七、完整示例对比

7.1 reserve + push_back

std::vector<int> v;
v.reserve(5);

for (int i = 0; i < 5; ++i) {
    v.push_back(i);
}

执行过程:

先预留空间
再逐个添加元素
最终 size 为 5

7.2 resize + 下标赋值

std::vector<int> v;
v.resize(5);

for (int i = 0; i < 5; ++i) {
    v[i] = i;
}

执行过程:

先创建 5 个元素
再逐个通过下标赋值
最终 size 为 5

7.3 错误示例

std::vector<int> v;
v.reserve(5);

for (int i = 0; i < 5; ++i) {
    v[i] = i; // 错误
}

原因:

reserve 只是预留空间
没有创建元素
v.size() 仍然是 0
不能通过下标访问不存在的元素

八、最终记忆版

最后可以用下面几句话记住:

push_back:对象已经有了,把它放进去。

emplace_back:对象还没有,用参数直接在容器里造一个。

reserve:只是提前占位置,不是真的有人坐。

resize:真的安排了 n 个座位,并且每个座位上都有一个元素。

在实际代码中,最常见的推荐组合是:

std::vector<Point> points;
points.reserve(10000);

for (int i = 0; i < 10000; ++i) {
    points.emplace_back(i, i + 1, i + 2);
}

这表示:

提前预留空间,减少扩容
然后直接在容器内部构造对象

这是使用 std::vector 时比较常见、也比较高效的写法。

posted @ 2026-05-17 19:36  Ytytyty  阅读(47)  评论(0)    收藏  举报