emplace_back()
详解:C++ 就地构造的效率革命
emplace_back()
是 C++11 引入的容器成员函数,用于在容器尾部就地构造(而非拷贝或移动)元素。这一特性显著提升了复杂对象的插入效率,尤其适用于构造代价较高的类型。
一、核心优势:就地构造,避免拷贝
传统的 push_back()
需要先构造一个临时对象,再将其拷贝或移动到容器中:
std::vector<std::string> vec;
vec.push_back("hello"); // 步骤1: 构造临时 string 对象
// 步骤2: 移动临时对象到 vector 中
// 步骤3: 销毁临时对象
而 emplace_back()
直接在容器尾部的内存空间中构造对象:
vec.emplace_back("hello"); // 直接在 vector 内存中构造 string 对象
// 无需临时对象,无需拷贝/移动
二、参数与原理
emplace_back()
的原型为:
template <class... Args>
void emplace_back(Args&&... args);
- 参数:
Args&&... args
是一个可变参数模板,接受任意数量和类型的参数 - 原理:通过完美转发(Perfect Forwarding)将参数传递给元素类型的构造函数
- 效果:直接在容器管理的内存中构造对象,无需临时对象
三、示例对比
1. 基本类型示例
std::vector<int> vec;
vec.push_back(42); // 拷贝 int 值
vec.emplace_back(42); // 直接构造 int 值
// 两者效率相同,因为 int 是 POD 类型
2. 复杂对象示例
class ExpensiveObject {
public:
ExpensiveObject(int x, double y) : x(x), y(y) {
// 复杂且耗时的初始化操作
}
// 拷贝构造函数(代价高)
ExpensiveObject(const ExpensiveObject& other) = delete;
// 移动构造函数(代价高)
ExpensiveObject(ExpensiveObject&& other) = delete;
};
std::vector<ExpensiveObject> vec;
// 错误:无法使用 push_back,因为需要拷贝或移动
// vec.push_back(ExpensiveObject(1, 2.0));
// 正确:emplace_back 直接构造对象
vec.emplace_back(1, 2.0); // 直接传递构造参数
四、完美转发与参数匹配
emplace_back()
支持直接传递构造所需的参数,包括:
- 构造函数参数
- 初始化列表
- 隐式类型转换参数
class Person {
public:
Person(std::string name, int age) : name(name), age(age) {}
private:
std::string name;
int age;
};
std::vector<Person> people;
// 使用 emplace_back 传递构造参数
people.emplace_back("Alice", 30); // 直接构造 Person 对象
// 使用 push_back 需要显式构造 Person
people.push_back(Person("Bob", 25)); // 先构造临时对象,再移动
五、与 push_back()
的关键区别
特性 | emplace_back() |
push_back() |
---|---|---|
参数 | 接受构造函数的参数包 | 接受已构造的对象(左值或右值) |
构造方式 | 就地构造,无需临时对象 | 需要先构造临时对象,再拷贝/移动 |
支持不可移动类型 | 支持(只要构造函数可用) | 不支持(必须可拷贝或可移动) |
隐式类型转换 | 支持(直接传递转换所需参数) | 需显式转换(或提供转换构造函数) |
六、注意事项
内存扩容:若容器需要重新分配内存,
emplace_back()
仍需移动所有现有元素异常安全:若构造函数抛出异常,容器状态保持不变
返回值:
emplace_back()
不返回新元素的引用(C++17 起emplace()
返回)优先使用场景:
- 插入复杂对象(如包含动态资源的类)
- 插入需要隐式类型转换的对象
- 插入不可拷贝/不可移动的对象
七、进阶应用:初始化列表参数
emplace_back()
可以正确处理初始化列表参数:
std::vector<std::vector<int>> matrix;
// 使用 emplace_back 和初始化列表
matrix.emplace_back({1, 2, 3}); // 直接构造内部 vector
// 等价于
matrix.push_back(std::vector<int>{1, 2, 3});
八、性能测试对比
以下代码对比了 push_back
和 emplace_back
的性能差异:
#include <chrono>
#include <vector>
#include <string>
#include <iostream>
struct ExpensiveToCopy {
std::string largeData;
ExpensiveToCopy(const char* data) : largeData(data) {}
// 模拟高代价的拷贝构造
ExpensiveToCopy(const ExpensiveToCopy& other) : largeData(other.largeData) {
// 模拟耗时操作
for (int i = 0; i < 1000; ++i) {}
}
};
int main() {
std::vector<ExpensiveToCopy> vec;
// 测试 push_back
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; ++i) {
vec.push_back("a very long string that needs to be copied");
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "push_back time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms" << std::endl;
// 测试 emplace_back
vec.clear();
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; ++i) {
vec.emplace_back("a very long string that needs to be copied");
}
end = std::chrono::high_resolution_clock::now();
std::cout << "emplace_back time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms" << std::endl;
return 0;
}
九、总结
emplace_back()
是 C++ 容器库的重要改进,它通过就地构造机制显著提升了插入效率,尤其适用于:
- 构造代价高昂的对象
- 需要隐式类型转换的对象
- 不可拷贝/不可移动的对象
在现代 C++ 编程中,建议优先使用 emplace_back()
替代 push_back()
,除非需要明确的类型检查或兼容性保证。