「这是我参与11月更文挑战的第 10 天,活动详情查看:2021最后一次更文挑战」。
参加该活动的第 21 篇文章
对比
大部分的容器在 C++11 中对于添加元素除了常见的 insert 或者 pusb_back/push_front 之外还提供一个新的方法叫做 emplace。 比如你想要向 std::vector 的末尾添加一个元素,示例代码如下:
/// @note 传统方式
std::vector<int> nums;
nums.push_back(1);
/// @note C++11
std::vector<int> nums;
nums.emplace_back(1);
优点
使用 emplace 的优点是避免产生不必要的临时变量,避免不必要的临时对象的产生,举个例子:
struct Foo {
Foo(int n, double x);
};
std::vector<Foo> v;
v.emplace(someIterator, 42, 3.1416); ///< 没有临时变量产生
v.insert(someIterator, Foo(42, 3.1416)); ///< 需要产生一个临时变量
v.insert(someIterator, {42, 3.1416}); ///< 需要产生一个临时变量
原理
从以上的例子中,我们可以看出 emplace 相较于 insert,emplace 的语法看起来比较特别,后面两个参数自动用来构造 vector 内部的 Foo 对象。这是因为其内部利用了 C++11 的两个新特性 —— 变参模板 和 完美转发。
- [变参模板] 使得
emplace可以接受任意参数,这样就可以适用于任意对象的构建。 - [完美转发] 使得接收下来的参数能够原样完美地传递给对象的构造函数,这带来另一个方便性就是即使是构造函数声明为
explicit它还是可以正常工作,因为它不存在临时变量和隐式转换。
struct Bar
{
Bar(int a) {}
explicit Bar(int a, double b) {} ///< 必须显示调用
};
int main(void)
{
vector<Bar> bv;
bv.push_back(1); ///< 隐式转换生成临时变量
bv.push_back(Bar(1)); ///< 显示构造临时变量
bv.emplace_back(1); ///< 没有临时变量
//bv.push_back({1, 2.0}); ///< 无法进行隐式转换
bv.push_back(Bar(1, 2.0));///< 显示构造临时变量
bv.emplace_back(1, 2.0); ///< 没有临时变量
return 0;
}
注意
值得注意的是
map类型的emplace处理比较特殊,因为和其他的容器不同,map的emplace方法把它接收到的所有的参数都一起转发给pair的构造函数。但是对于一个pair来说,它既需要构造它的key又需要构造它的value。如果我们按照之前普通的语法使用变参模板的话,则它是无法区分哪些参数用来构造key, 哪些用来构造value的。 比如下面的代码// @note 无法区分哪个参数用来构造 key 哪些用来构造 value // 有可能是 string s("hello", 1), complex<double> cpx(2) // 也有可能是 string s("hello"), complex<double> cpx(1, 2) map<string, complex<double>> scp; scp.emplace("hello", 1, 2);