emplace_back与push_back的核心区别在于对象构造方式:前者在vector内存中直接构造,后者先构造再移动;性能差异主要体现在非平凡类型上,如string或含unique_ptr的类。

emplace_back 和 push_back 的核心区别不在“能不能加元素”,而在于「对象在哪造、怎么造」——前者就地构造,后者先造再搬。对多数人来说,这不是语法偏好问题,而是能否避免一次构造+一次析构+可能的拷贝/移动开销的实际性能取舍。
传参方式不同:参数是对象 vs 参数是构造参数
push_back 只接受一个已存在的对象(左值或右值),它不关心你怎么来的,只负责把它塞进 vector;emplace_back 接收的是构造该对象所需的原始参数,由 vector 在内部内存上直接调用构造函数。
常见错误现象:vec.emplace_back({1, "Alice"}) 编译失败,因为花括号初始化列表无法被完美转发;应改用 vec.emplace_back(1, "Alice")。
- 正确用法:
vec.push_back(Person(1, "Alice"))(临时对象 → 移动) - 等效但更优:
vec.emplace_back(1, "Alice")(直接在 vector 内存中调用Person(int, const char*)) - 已有对象必须用
push_back:Person p(2, "Bob"); vec.push_back(p);——emplace_back无法接收对象引用并“再构造”
性能差异真实存在,但只在特定类型上明显
对 int、double、指针等 trivial 类型,两者生成的汇编几乎一致,无实际差别;真正拉开差距的是管理堆资源的对象,比如 std::string、std::vector<t></t>、自定义类含 std::unique_ptr 等。
原因在于:即使 push_back 用右值,仍需先调用一次构造函数创建临时对象(触发一次堆分配),再 move 构造(可能触发第二次堆分配或 memcpy),最后析构临时对象;emplace_back 跳过临时对象,仅一次构造 + 一次堆分配。
立即学习“C++免费学习笔记(深入)”;
- 典型耗时场景:
std::vector<:string></:string>插入长字符串,emplace_back("hello world...")比push_back(std::string("hello world..."))少一次小字符串拷贝和临时对象生命周期管理 - 注意:如果类没有匹配的构造函数(如只有
explicit构造函数且参数类型不完全匹配),emplace_back会编译失败,而push_back可能隐式转换后成功
容易被忽略的兼容性与语义陷阱
emplace_back 依赖完美转发(std::forward),这意味着它对参数类型的推导更“激进”,某些看似等价的写法行为完全不同。
-
vec.emplace_back(s)(s是std::string&)→ 实际调用std::string的拷贝构造,不是原地构造!因为转发后仍是左值引用 -
vec.emplace_back(std::move(s))→ 仍不是“就地”,只是把s移动出去,再用移动后的值构造新对象,反而破坏原对象 - 真正安全的就地构造,只发生在你传入**非引用、非已有对象**的原始参数时,例如
(42, "name", true) - 调试时若发现构造/析构次数没减少,大概率是误传了变量名而非字面量或临时值
什么时候该换、什么时候别动
不必为“用新不用旧”而强行替换;关键看上下文是否满足就地构造的前提。
- 推荐换:
vec.emplace_back(10, "user", Status::Active)这类参数明确、无中间状态、类有对应构造函数的场景 - 不该换:
vec.push_back(user_obj)(已有对象)、vec.push_back(*it)(迭代器解引用)、vec.push_back(get_person())(返回值已构造好) - 要警惕:
emplace_back在模板代码中可能因 SFINAE 失败而静默退化为编译错误,而push_back更宽容



















