在C++标准库的vector容器中,向尾部添加元素最常用的两个接口是push_back和emplace_back。它们表面上都能完成插入,但在对象构造路径上存在本质区别。push_back接收的是已经构造好的对象,而emplace_back接收构造参数,并借助完美转发在容器内部直接构造。这种机制差异会在特定场景中带来显著的性能变化。

一、底层机制差异
push_back有两种重载形式:一种接受const引用,另一种接受右值引用。当传入左值时会调用拷贝构造函数,传入临时对象时会优先匹配移动构造。无论哪种情况,对象都必须在调用前完成一次构造,随后再被拷贝或移动到vector的存储空间中。这意味着至少发生一次额外的构造或移动操作。
emplace_back则不同,它使用可变参数模板和完美转发,将参数原样传递给元素类型的构造函数。vector会先分配或复用一块原始内存,然后在这块内存上直接调用构造函数,省去了外部的临时对象构造以及后续的拷贝或移动。对于没有移动构造或者移动成本较高的类型,这种差异尤其明显。
#include <vector>
#include <string>
struct Data {
int id;
std::string name;
Data(int i, const std::string& n) : id(i), name(n) {
// 构造函数
}
};
int main() {
std::vector<Data> v;
v.reserve(10);
// push_back:先构造临时Data,再移动或拷贝进vector
v.push_back(Data(1, "test"));
// emplace_back:直接在vector内存中构造Data
v.emplace_back(2, "demo");
return 0;
}
二、性能测试设计
为了直观对比,我们设计三组测试:基础类型int、包含显式拷贝构造和移动构造的自定义类型、以及只支持移动的大对象。每组执行十万次插入,并使用chrono计时。为避免vector扩容干扰,提前调用reserve预留空间。
测试环境为普通桌面编译器开启O2优化。自定义类型在构造、拷贝、移动时分别打印或计数,以便确认调用路径。通过统计耗时和构造次数,可以清楚看到emplace_back在自定义类型上的优势,而在int这类平凡类型上两者几乎无差。
#include <vector>
#include <chrono>
#include <iostream>
struct Heavy {
int buf[64];
Heavy() {}
Heavy(const Heavy&) { /* 拷贝开销大 */ }
Heavy(Heavy&&) { /* 移动开销小 */ }
};
int main() {
const int N = 100000;
{
std::vector<Heavy> v;
v.reserve(N);
auto t1 = std::chrono::steady_clock::now();
for (int i = 0; i < N; ++i) {
v.push_back(Heavy());
}
auto t2 = std::chrono::steady_clock::now();
std::cout << "push_back: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count()
<< " msn";
}
{
std::vector<Heavy> v;
v.reserve(N);
auto t1 = std::chrono::steady_clock::now();
for (int i = 0; i < N; ++i) {
v.emplace_back();
}
auto t2 = std::chrono::steady_clock::now();
std::cout << "emplace_back: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count()
<< " msn";
}
return 0;
}
三、测试结果与分析
在自定义Heavy类型的测试中,push_back由于先构造临时对象再移动,总耗时通常比emplace_back高出一定比例。emplace_back直接在容器内构造,减少了一次移动构造调用。对于int等平凡类型,两者生成的汇编几乎一致,性能差异可以忽略。
需要注意,如果元素类型没有 noexcept 移动构造,vector扩容时仍可能使用拷贝构造,此时emplace_back也无法避免拷贝。另外,emplace_back的参数转发有时会导致意料之外的构造函数匹配,例如用单个整数去构造本应接收初始化列表的对象,因此代码可读性要权衡。
| 类型 | push_back耗时 | emplace_back耗时 | 差异原因 |
|---|---|---|---|
| int | 约3ms | 约3ms | 平凡构造无额外开销 |
| Heavy(有移动构造) | 约12ms | 约8ms | 少一次移动构造 |
| 无移动构造对象 | 约25ms | 约25ms | 均退化为拷贝 |
四、使用建议
当插入的是临时对象且类型移动成本低时,push_back和emplace_back差距不大;但当类型构造昂贵、或需要避免额外移动、或参数为多段构造值时,优先使用emplace_back。对于基础类型,按代码清晰度选择即可。
在泛型代码中,若不确定元素类型,emplace_back通常更安全且高效,但仍需测试验证。不要盲目替换所有push_back,应结合性能剖析与构造函数设计,才能发挥C++零开销抽象的优势。
emplace_backpush_backC++_性能测试修改时间:2026-08-02 05:54:30