在 C++ 的 std::vector
中,push_back
和 emplace_back
都是用于向容器末尾添加元素的成员函数,但它们的实现机制和使用场景有显著区别。
以下是两者的核心差异:
1. 参数传递方式
-
push_back
接受一个已构造的对象作为参数(拷贝或移动):-
若传递左值(已存在的对象),会调用拷贝构造函数。
-
若传递右值(临时对象或
std::move
的结果),会调用移动构造函数(如果存在)。
std::vector<std::string> vec; std::string s = "hello"; vec.push_back(s); // 拷贝构造(s 是左值) vec.push_back("world"); // 移动构造(临时对象是右值)
-
-
emplace_back
直接通过参数包(variadic arguments)在容器内存中就地构造对象,避免临时对象的拷贝或移动。
它使用**完美转发(perfect forwarding)**将参数传递给元素的构造函数。vec.emplace_back(3, 'a'); // 直接在 vector 内构造 std::string(3, 'a')
2. 性能差异
-
对于需要拷贝/移动的类型(如非平凡类型
std::string
、自定义类):-
emplace_back
通常更高效,因为它跳过临时对象的构造。 -
push_back
可能多一次拷贝/移动操作。
class MyClass { public: MyClass(int a, int b) { /*...*/ } MyClass(const MyClass&) { std::cout << "Copy\n"; } MyClass(MyClass&&) { std::cout << "Move\n"; } }; std::vector<MyClass> vec; vec.push_back(MyClass(1, 2)); // 1. 构造临时对象 -> 2. 移动构造到容器 vec.emplace_back(1, 2); // 直接构造在容器内(无拷贝/移动) //输出: Move // push_back (无输出) // emplace_back
-
-
对于简单类型(如
int
、double
):
两者性能几乎无差别。
3. 使用场景
-
优先使用
emplace_back
:
当需要直接通过构造函数参数添加元素时,尤其是构造参数较多或对象较大时。vec.emplace_back("hello", 5); // 直接调用 std::string(const char*, size_t)
-
使用
push_back
:
当已经有一个存在的对象需要添加到容器中(尤其是需要拷贝时)。std::string existing_str = "hello"; vec.push_back(existing_str); // 显式拷贝
4. 类型要求
-
push_back
:
要求元素类型可拷贝构造或移动构造。 -
emplace_back
:
要求元素类型有与参数包匹配的构造函数。
5. 返回值(C++17 起)
-
push_back
:无返回值(void
)。 -
emplace_back
:返回新插入元素的引用(reference
)。
可链式调用:vec.emplace_back(1, 2).doSomething();
6. 显式构造函数调用
emplace_back
可能因参数隐式转换导致意外行为,需注意显式构造函数:
class Wrapper {
public:
explicit Wrapper(int x) { /*...*/ } // explicit 构造函数
};
std::vector<Wrapper> vec;
vec.push_back(10); // ❌ 错误:不能隐式转换 int 到 Wrapper
vec.push_back(Wrapper(10)); // ✅ 需显式构造
vec.emplace_back(10); // ✅ 合法:直接调用 Wrapper(int)
总结
特性 | push_back | emplace_back |
---|---|---|
参数类型 | 对象(左值/右值) | 构造参数包 |
临时对象构造 | 需要(可能触发拷贝/移动) | 不需要(直接就地构造) |
性能(非平凡类型) | 较低(多一次拷贝/移动) | 较高 |
适用场景 | 已有对象需要拷贝/移动时 | 直接通过参数构造新对象时 |
返回值 | void | 新元素的引用(C++17 起) |
建议:
-
默认优先使用
emplace_back
,尤其是构造对象时代码更简洁且性能更优。 -
若需强制拷贝或移动语义,或处理显式构造函数时,使用
push_back
。