emplace_back能绕过临时对象构造,因为它直接在vector内存中调用元素构造函数原地构造,而push_back需先构造临时对象再移动/拷贝。

为什么 emplace_back 能绕过临时对象构造
调用 push_back 时,如果传入的是一个已构造好的对象(比如 MyClass{1, "hello"}),编译器必须先调用构造函数生成临时对象,再调用移动构造(或拷贝构造)把它塞进 vector 的内存里。而 emplace_back 直接把参数转发给元素类型的构造函数,在 vector 内部预留的内存原地构造——跳过了临时对象这层中转。
常见错误现象:push_back(MyClass{a, b}) 看似简洁,但若 MyClass 没有移动构造函数(或被 = delete),就会退化为拷贝构造,甚至编译失败;emplace_back(a, b) 则完全不依赖 MyClass 是否可移动/可拷贝。
- 适用前提:目标类型必须支持对应参数列表的构造函数(否则编译报错)
- 若参数本身是左值(如变量
x、y),emplace_back(x, y)仍会原地调用构造函数,不会额外拷贝x、y,但要注意引用绑定规则 - 对 POD 类型或 trivially copyable 类型,差异可能被优化掉,但语义上仍是两回事
emplace_back 和 push_back 在移动语义下的表现差异
即使类定义了移动构造函数,push_back 仍需一次移动操作;emplace_back 连这次移动都省了。尤其当移动构造函数有副作用(比如日志、计数、资源登记),这个差别就不可忽略。
示例:
立即学习“C++免费学习笔记(深入)”;
struct Heavy {
Heavy(int x) { /* 分配 1MB 内存 */ }
Heavy(Heavy&& other) noexcept { /* 移动指针,但要 log("moved") */ }
};
vector v;
v.push_back(Heavy{42}); // 构造 → log("moved")
v.emplace_back(42); // 构造(仅一次,无 log) - 编译器无法在
push_back中省略移动(除非 RVO 介入,但 vector 插入不适用) -
emplace_back的参数完美转发机制意味着它能触发更精确的构造函数重载(比如explicit构造函数、带std::initializer_list的构造函数) - 注意:如果构造函数抛异常,
emplace_back的异常安全行为与push_back一致(不破坏 vector 状态)
什么情况下 emplace_back 反而更慢或出问题
不是所有场景都适合无脑换用 emplace_back。最典型的坑是参数求值顺序未定义 + 副作用冲突,或者隐式转换导致意外调用错误构造函数。
- 避免写
v.emplace_back(func1(), func2()),若func1()抛异常,func2()可能已执行且无法回滚 - 若类有多个构造函数,
emplace_back(0)可能调用MyClass(bool)而非预期的MyClass(int),引发静默逻辑错误 - 对只含默认构造函数的类型(如
vector),emplace_back()和push_back({})效果相同,但可读性不如push_back(0) - 调试时,
emplace_back的模板展开错误信息往往比push_back更难读
实测性能差距到底有多大
真实影响取决于对象大小、构造开销、是否启用优化。小对象(如 pair)在 -O2 下差异常小于 5%;大对象(含动态分配、深拷贝)或禁用移动语义时,emplace_back 可快 2× 以上。
关键点在于:性能收益来自减少一次构造+一次析构(临时对象生命周期),而不是“避免拷贝”这么简单。如果构造函数本身很重(比如打开文件、解析 JSON),那省掉这一次就是实打实的节省。
- 用
clang++ -fsanitize=address,undefined测试时,emplace_back不会触发临时对象的析构漏检(而push_back可能因优化掩盖问题) - 对于
vector,> emplace_back(new T)是错的(应传std::make_unique),这里容易误用 raw pointer 导致内存泄漏() - 真正影响效率的,往往是构造函数内部逻辑,而不是
emplace_back本身——它只是让构造发生得更直接
别只盯着函数名,重点看你的类型构造成本和参数来源是否可控。临时对象这层抽象,有时候是保护,有时候是累赘。










