每周技巧 #65:把东西放到该放的位置
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #65: Putting Things in their Place。
原文最初作为 totw/65 发布于 2013 年 12 月 12 日。
作者:Hyrum Wright
“让我解释一下。不,太多了。让我总结一下。”– Inigo Montoya
C++11 为向标准容器插入元素添加了一种新方式:emplace() 系列方法。这些方法会直接在容器内部创建对象,而不是先创建一个临时对象,再把该对象复制或移动到容器中。对几乎所有对象来说,避免这些拷贝都更高效;它也让在标准容器中存储只可移动对象(例如 std::unique_ptr)更容易。
旧方式和新方式
我们用 vector 看一个简单例子,对比两种风格。第一个例子使用 C++11 之前的代码:
|
|
使用较旧的 push_back() 方法时,会构造两个 Foo 对象:一个是临时实参,另一个是 vector 中从该临时对象移动构造出来的对象。
我们可以改用 C++11 的 emplace_back(),这样只有一个对象会直接在 vector 的内存中构造。由于 “emplace” 系列函数会把参数转发给底层对象的构造函数,我们可以直接提供构造函数参数,不再需要创建临时 Foo:
|
|
对只可移动操作使用 emplace 方法
到目前为止,我们看到的是 emplace 方法改善性能的情况。但它们也能让之前不可能的代码变得可行,例如在容器中存储 std::unique_ptr 这样的只可移动类型。考虑这段代码:
|
|
你会如何向这个 vector 插入值?一种方式是使用 push_back(),并直接在它的实参中构造值:
|
|
这种语法能工作,但有点笨重。遗憾的是,绕开这种困惑的传统方式充满复杂性:
|
|
这段代码可以编译,但在插入之前,原始指针的所有权并不清晰。更糟的是,vector 现在拥有这个对象,但 f2 仍然有效,之后可能被意外删除。对不了解情况的读者来说,这种所有权模式可能很困惑,尤其是构造和插入不像上面这样连续发生时。
其他方案甚至无法编译,因为 unique_ptr 不可拷贝:
|
|
使用 emplace 方法,可以让你在创建对象的同时插入它,这更直观。在其他情况下,如果你需要把 unique_ptr 移入 vector,也可以这样做:
|
|
把 emplace 和标准迭代器结合起来,也可以在 vector 的任意位置插入对象:
|
|
不过,从实践角度看,我们并不希望看到这些构造 unique_ptr 的方式。请使用 std::make_unique(C++14 起)或 absl::make_unique(如果你仍在 C++11)。
结论
本技巧中我们用 vector 做例子,但 emplace 方法也适用于 map、list 和其他 STL 容器。和 unique_ptr 结合使用时,emplace 能带来良好封装,并让堆分配对象的所有权语义以前所未有的方式变得清晰。希望这能让你感受到容器方法中新 emplace 家族的力量,并愿意在自己的代码中恰当地使用它们。