许多 C++ 开发者一直对 push_back() 和 emplace_back() 的区别感到困惑:它们究竟有何不同?这次我直接查阅了 libstdc++ 的源代码,终于彻底理解了底层实现的本质差异。下面把我的核心发现分享出来,帮助大家在实际开发中做出更高效的选择。

- allocate 源码:gcc.gnu.org/onlinedocs/…
- push_back 实现:gcc.gnu.org/onlinedocs/…
- emplace_back 实现:gcc.gnu.org/onlinedocs/…
先来看 push_back() 的工作流程。它要求传入一个已经构造好的对象(可以是左值或右值),然后在容器内部通过拷贝构造或移动构造来创建副本。换句话说,你传入一个临时对象,容器内部还需要再“复制”或“移动”一次,这就会多产生一次对象构造的开销。
// GCC libstdc++ vector
void push_back(const value_type& __x) {
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
// 空间足够,在已开辟的内存上【拷贝构造】新对象
this->_M_impl.construct(this->_M_impl._M_finish, __x);
++this->_M_impl._M_finish;
} else {
// 空间不足,扩容并移动/拷贝旧数据
_M_realloc_insert(end(), __x);
}
}void push_back(value_type&& __x) {
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
// 空间足够,在已开辟的内存上【移动构造】新对象
this->_M_impl.construct(this->_M_impl, this->_M_impl._M_finish, std::move(__x));
++this->_M_impl._M_finish;
} else {
_M_realloc_insert(end(), std::move(__x));
}
}void
construct(pointer __p, const _Tp& __val)
{ ::new((void *)__p) _Tp(__val); } /* 直接进行拷贝/移动构造 */
而 emplace_back() 走的则是完全不同的路径——它接收的不是已经构造好的对象,而是元素类型构造函数的参数。通过可变模板参数和完美转发机制,它直接在容器预留的内存上调用元素的构造函数,从而省去了创建临时对象再拷贝或移动的中间环节,在性能上更优。
template<typename... _Args>
void
emplace_back(_Args&&... __args) {
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
// 空间足够,直接在目标内存上【就地构造】
this->_M_impl.construct(this->_M_impl, this->_M_impl._M_finish,
std::forward<_Args>(__args)...);
++this->_M_impl._M_finish;
} else {
_M_realloc_insert(end(), std::forward<_Args>(__args)...);
}
}template<typename... _Args> /* 可变模版参数 */
void
construct(pointer __p, _Args&&... __args)
{ ::new((void *)__p) _Tp(std::forward<_Args>(__args)...); } /* 完美转发 */
简单总结一下两者的核心区别:
- 接口调用方式不同:
push_back()接收的是已经构造好的对象(左值或右值引用),而emplace_back()接收的是元素类型的构造参数(理解起来可能有点绕,但掌握后非常直观)。 - 底层实现机制不同:
push_back()需要先在外层创建临时对象,再通过拷贝或移动构造将其“搬入”容器;emplace_back()则直接在容器预留的内存上通过完美转发调用构造函数,完全省去了临时对象的创建与拷贝/移动步骤。对于复杂对象(如包含动态申请内存、资源管理的类型),这个性能差异非常明显,在 C++ 开发中值得优先考虑。
