push_back 传左值时调用一次拷贝或移动构造,传右值时优先移动构造;emplace_back 直接就地构造,避免中间对象,仅当参数匹配目标构造函数时才真正减少构造次数。

push_back 会触发几次构造函数?
当传入一个已存在的对象时,push_back 一定调用一次拷贝构造(或移动构造,如果对象支持且满足条件);如果传入的是临时对象或右值,编译器可能优化掉拷贝(RVO/NRVO),但语义上仍要求类型具备可拷贝/可移动性。
常见错误现象:std::vector 编译失败——因为 NonCopyable 禁用了拷贝和移动,而 push_back 的重载签名强制要求可移动(C++11 起)。
- 传左值:调用拷贝构造(或移动构造,若左值被
std::move显式转换) - 传右值:优先调用移动构造;若类型不可移动,则退化为拷贝构造
- 即使对象内部有优化(如小字符串优化),外部容器操作仍需完成一次完整构造过程
emplace_back 如何减少构造次数?
emplace_back 直接在 vector 尾部的未初始化内存上调用类的构造函数,绕过中间对象的创建。它接受任意参数列表,完美转发给目标类型的构造函数。
使用场景:构造开销大、不可拷贝/不可移动、或带多个参数的类型(如 std::string("hello")、std::pair)。
立即学习“C++免费学习笔记(深入)”;
- 对
std::vector<:string>执行v.emplace_back("hello"):只调用一次std::string的 const char* 构造函数 - 等价的
v.push_back(std::string("hello")):先构造临时std::string,再移动(或拷贝)进 vector,至少两次构造行为 - 若类型没有匹配的构造函数(比如只提供默认构造+赋值),
emplace_back会编译失败,而非静默降级
何时 emplace_back 反而更慢?
不是所有情况都适合无脑换用 emplace_back。它的优势依赖于“就地构造”能省掉的步骤是否真实存在。
性能陷阱:
- 对 trivial 类型(如
int、std::array),两者生成的汇编几乎一致,无实际差异 - 若参数本身就需要构造临时对象(如
v.emplace_back(get_string(), get_int())),这些临时对象仍会先被构造,再转发——只是 vector 内部不额外拷贝 - 调试模式下,
emplace_back的模板展开可能拖慢编译速度,且错误信息更难读(尤其参数类型不匹配时) - 某些标准库实现对小对象的
push_back做了移动优化,实际观测不到差别
实测构造次数验证方法
最直接的方式是写一个带计数器的测试类,重载各构造函数并打印日志:
struct Tracked {
static int ctor_count, copy_count, move_count;
Tracked() { ++ctor_count; }
Tracked(int) { ++ctor_count; }
Tracked(const Tracked&) { ++copy_count; }
Tracked(Tracked&&) { ++move_count; }
};
int Tracked::ctor_count = 0, Tracked::copy_count = 0, Tracked::move_count = 0;
然后分别运行:
-
v.push_back(Tracked{42});→ 观察ctor_count和move_count -
v.emplace_back(42);→ 通常只有ctor_count+1 -
v.push_back(Tracked{});→ 若Tracked{}是左值,还会触发一次移动或拷贝
注意:编译器优化(-O2)可能内联或消除部分调用,建议关掉优化或加 volatile 强制保留日志逻辑来观察真实行为。
真正容易被忽略的是:emplace_back 的“零拷贝”优势只在构造函数参数能直接传递、且目标类型确实支持对应构造签名时才成立。传错参数类型不会报“构造失败”,而是报一长串模板推导错误——这时候回头检查构造函数声明比硬调参数更省时间。










