emplace_back核心区别在于直接在容器末尾构造元素,不经过临时对象;push_back需先构造临时对象再拷贝或移动。

emplace_back 和 push_back 的核心区别在哪
emplace_back 不是“更快的 push_back”,它是绕过临时对象、直接在容器末尾构造元素的机制。关键在于:它把参数原样转发给元素类型的构造函数,不经过拷贝或移动;而 push_back 必须先构造一个临时对象(或接受右值并触发移动),再把它塞进容器。
这意味着:如果元素类型构造开销大、且没有廉价的移动构造函数,emplace_back 就有实际优势;否则,两者生成的汇编可能完全一致。
什么时候 emplace_back 真正省掉了一次构造
常见错误现象:push_back(MyClass{a, b}) 看似简洁,实则先调用 MyClass(a, b) 构造临时对象,再调用移动构造函数(若存在)或拷贝构造函数(若移动被禁用)——两次构造。
而 emplace_back(a, b) 直接在 vector 末尾内存上调用 MyClass::MyClass(int, int),一次到位。
立即学习“C++免费学习笔记(深入)”;
使用场景:
- 元素类型有显式定义的多参构造函数(如
std::pair<int std::string></int>) - 类禁用了拷贝/移动(比如含
std::mutex成员) - 容器存储的是非 trivial 类型,且构造逻辑较重(如带资源分配的 wrapper)
注意:emplace_back 对 int、std::string(已优化的短字符串)、std::shared_ptr 等类型几乎无差别,别为这点“理论优势”强行改写。
容易踩的坑:完美转发失效和隐式转换陷阱
emplace_back 依赖模板参数推导 + std::forward,一旦参数类型不匹配,可能触发意外的构造函数,甚至编译失败。
常见错误现象:
- 传入字面量
0给期望std::string&&的构造函数,结果匹配到std::string(size_t),构造出空字符串 - 传入
nullptr导致调用std::unique_ptr<t>(decltype(nullptr))</t>而非预期的默认构造 - 引用类型被转发后绑定到临时对象,造成悬垂引用(尤其在 lambda 捕获或返回局部对象时)
实操建议:
- 避免对已有变量直接
emplace_back(x),除非明确知道 x 的类型和目标构造函数签名 - 优先用
{}初始化语法(如emplace_back({a, b}))来限制重载解析范围 - 在调试时加断点看实际调用了哪个构造函数——很多“没变快”是因为根本没走你预想的路径
性能差异到底有多大?别猜,得测
差异不是固定倍数,取决于三件事:元素类型的构造成本、是否启用 RVO/NRVO、编译器优化等级(-O2 下很多差异会消失)。
实操建议:
- 测试必须用 Release 模式,关闭调试断言(
_ITERATOR_DEBUG_LEVEL=0) - 对比要控制变量:同一容器、同一元素类型、同一插入规模(至少 10k 次以上)
- 关注分配次数(可用自定义 allocator 或
malloc_hook观察),而非单纯 wall-clock 时间
典型反例:对 std::vector<:string></:string> 插入小字符串,emplace_back("hello") 和 push_back("hello") 在 Clang 15 + libc++ 下生成的指令完全相同——因为 string 的 const char* 构造函数被内联,且短字符串优化让移动也极廉价。
真正拉开差距的,往往是自定义类里带 new、文件打开、锁获取等操作的构造函数。
这种差异不在语法层面,而在你是否真的让对象“就地出生”。写错一个参数类型,就退回两步。










