emplace_back vs push_back 详解

emplace_back vs push_back 详解(面试标准版)


一、一句话定位

push_back 把一个已经构造好的对象 拷贝/移动进容器;emplace_back 把构造参数完美转发 到容器内存上原地构造,省去中间临时对象。

cpp 复制代码
struct Person {
    Person(string n, int a) : name(std::move(n)), age(a) {}
    string name; int age;
};
vector<Person> v;
v.push_back(Person("Tom", 18));   // ①先构造临时Person → ②移动进容器(临时对象销毁)
v.emplace_back("Tom", 18);        // 直接在容器内存上用("Tom",18)构造 → 无临时对象

二、执行流程对比图

ini 复制代码
═══ push_back(Person("Tom",18)) ═══          ═══ emplace_back("Tom",18) ═══
 堆上构造临时对象                              容器扩容后的目标内存
 ┌─────────────┐                              ┌─────────────┐
 │ name="Tom"  │                              │             │
 │ age = 18    │                              │             │
 └─────────────┘                              │             │
      │ 移动构造(Move)                          ▲ 直接在这里调用
      ▼                                       │ Person("Tom",18)
 容器内存 ┌─────────────┐                      │
          │ name="Tom" │ ◄────────────────────┘
          │ age = 18   │
          └─────────────┘                      ┌─────────────┐
      临时对象析构 💀                           │ name="Tom"  │
                                               │ age = 18    │
 总计: 构造1 + 移动1 + 析构1                    └─────────────┘
                                               总计: 构造1 ✅ 完事

三种重载的完整真相(push_back 其实也有移动版本):

cpp 复制代码
push_back(const T&);   // C++03: 拷贝版 → lvalue走这里
push_back(T&&);        // C++11: 移动版 → 右值临时对象走这里
emplace_back(Args&&...); // 万能引用+完美转发,任意参数

所以对右值临时对象 ,push_back 的开销 = 1次构造 + 1次移动;emplace_back = 1次构造。差距只有那一次移动(对string/vector这类有堆资源的对象,移动虽便宜但不是零成本;对纯POD则几乎无差别)。


三、emplace_back 的真正优势场景(不只是省移动)

场景1:构造函数本身有开销时,省掉整个临时对象

cpp 复制代码
// 构造函数内部做重量级工作(如解析、分配)
v.push_back(BigData("very long string...", 1000, config));
//        └─ 先完整构造临时对象,再移动
v.emplace_back("very long string...", 1000, config);
//        └─ 参数直接转发,只构造一次,临时对象从未存在

场景2:禁止移动/拷贝的类型,push_back 根本用不了

cpp 复制代码
class Widget {
public:
    Widget(int x);
    Widget(const Widget&) = delete;
    Widget(Widget&&) = delete;      // 不可拷贝不可移动!
};
vector<Widget> v;
v.push_back(Widget(42));    // ❌ 编译错误:需要移动
v.emplace_back(42);         // ✅ 唯一选择:原地构造
// 这就是 mutex、atomic、lock_guard 等类型进容器的唯一方式

场景3:隐式转换行为的差异(隐藏陷阱⚠️)

cpp 复制代码
vector<string> vs;
vs.push_back("hello");    // ✅ 安全:隐式构造临时string再移动
                          //    push_back参数类型固定是string,不会意外调错
vs.emplace_back("hello"); // ✅ 也行,但...
vs.emplace_back(42);      // ❌ 危险! 完美转发会匹配string(int)的
                          //    explicit构造? 不,explicit挡住了→编译错
                          //    但如果构造函数非explicit就静默构造了!
vs.emplace_back(nullptr); // ⚠️ 匹配到 string(const char*)? nullptr是
                          //    nullptr_t → 编译错,但某些类型可能
                          //    匹配到意想不到的重载

要点explicit 构造函数在 emplace_back 的直接初始化语境下会被调用emplace_back内部是直接初始化),而 push_back(T&&) 拷贝初始化语义下 explicit 不参与隐式转换------所以 push_back 的类型检查更严格、更安全;emplace_back 更高效但更"放得开"。


四、对比总结表

维度 push_back emplace_back
来源 C++03(右值版C++11) C++11
参数 已构造的 const T& / T&& 构造参数包 Args&&...
机制 拷贝/移动已有对象 完美转发参数,容器内存上原地构造
开销(右值入容器) 1构造 + 1移动 1构造
开销(lvalue入容器) 1拷贝 同样1拷贝(无优势!)
支持不可移动类型 ✅(mutex等唯一途径)
类型安全 严格(固定T类型) 较宽松(explicit直接初始化可命中)
扩容时的行为 相同:都需要移动已有元素,都可能触发allocator 相同
关键澄清(防杠点)
  • lvalue 时两者一样都是拷贝,emplace_back 没有任何优势:

    cpp 复制代码
    Person p("Tom", 18);
    v.push_back(p);          // 拷贝
    v.emplace_back(p);       // 也是拷贝! 别以为emplace就快
  • 元素是 int/double等POD 时两者差异可忽略;

  • 移动构造 的类,差距只有一次廉价移动------性能收益经常被高估

  • 真正不可替代的是:不可移动类型 + 构造参数转发


五、底层实现原理(追问必备)

cpp 复制代码
// emplace_back 的核心:allocator 的 construct + 完美转发
template <typename... Args>
reference emplace_back(Args&&... args) {
    if (size_ == capacity_) realloc();          // 扩容逻辑完全一样
    alloc_.construct(data_ + size_,             // 在这块原始内存上
                     std::forward<Args>(args)...); // 转发参数构造
    return *data_ + size_++;
    //        └─ 完美转发: 左值参数保持左值,右值保持右值,
    //           最终调用 T(Args...) 匹配最合适的构造函数
}

关键点:容器内存是未初始化的原始内存push_back 用"构造临时+移动/拷贝"两步;emplace_back 用 placement new 直接一步构造。扩容行为两者完全一致。


六、高频追问速答

追问 答案
emplace_back 一定比 push_back 快吗? 不一定。lvalue时相同;有移动构造时只省一次廉价移动;真正收益在无移动类型和重构造场景。可读性优先时push_back更明确
为什么emplace_back能绕过移动? 它拿到的是构造参数而非对象,用placement new在容器内存直接调构造函数
完美转发失败的情况? NULL/位域/重载歧义参数时,转发后可能匹配到意外构造函数;{1,2}初始化列表不能转发------v.emplace_back({1,2})编译错,要用v.push_back({1,2})
emplace_back返回什么? C++17起返回新元素的引用 ,可以链式操作:v.emplace_back(x).doSomething()
insert 和它们的关系? insert在任意位置 插入(版本同push_back),C++17也有emplace对应版emplace(pos, args...)
什么时候坚持用push_back? ①想要显式类型转换检查 ②传初始化列表 ③代码意图清晰:push_back(make_...()) 一眼看懂对象来源

七、30秒总结话术(背诵版)

"push_back接收已构造对象,右值走移动版;emplace_back用万能引用接收构造参数包,完美转发后在容器内存上placement new原地构造,省掉临时对象的构造+移动。三者差异:右值入容器时emplace省一次移动;不可拷贝不可移动的类型如mutex只能用emplace ;lvalue传入时两者都是拷贝、无差别。注意emplace_back因为走直接初始化,explicit构造函数也会被匹配,类型安全不如push_back严格,而且初始化列表参数只能用push_back。所以我的习惯是:默认push_back语义清晰,需要参数转发或类型不可移动时用emplace_back,而不是无脑全用emplace。"


相关推荐
老孙讲技术2 小时前
把没网没电的户外点位接进出画值班台:unBindDeviceInfo 核 SIMCard 与 bindDeviceLive
后端·物联网·音视频开发
万物智能2 小时前
硬件调试三板斧—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
后端·架构
小夏coding3 小时前
从"一把梭"到"精妙拆解" —— 滑动窗口计时框架的设计演进
java·后端
长大19883 小时前
Node.js 22+ 特性实战:用内置能力写微服务与 CLI,能少装一堆依赖
后端
苍何3 小时前
飞书,成了豆包工作的“超级上下文”
后端
苍何3 小时前
会用 AI 做图,已经不够了(分享 3 个牛逼设计 skill)
后端
明月_清风3 小时前
发现一个超系统的 AI Agent 学习地图 —— Agent Atlas 推荐
人工智能·后端·agent
大勇前进4 小时前
设计模式在 JS 中的应用:用 TS 重写经典模式,结合 React / Vue 真实业务场景
后端
MacroZheng4 小时前
同事问我:"Claude Code经常失忆,不怕它把项目搞炸?",我:"怕,三个Markdown文件给它装个永不丢失的外置大脑!"
java·人工智能·后端