每周技巧 #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 之前的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Foo {
 public:
  Foo(int x, int y);
  
};

void addFoo() {
  std::vector<Foo> v1;
  v1.push_back(Foo(1, 2));
}

使用较旧的 push_back() 方法时,会构造两个 Foo 对象:一个是临时实参,另一个是 vector 中从该临时对象移动构造出来的对象。

我们可以改用 C++11 的 emplace_back(),这样只有一个对象会直接在 vector 的内存中构造。由于 “emplace” 系列函数会把参数转发给底层对象的构造函数,我们可以直接提供构造函数参数,不再需要创建临时 Foo

1
2
3
4
void addBetterFoo() {
  std::vector<Foo> v2;
  v2.emplace_back(1, 2);
}

对只可移动操作使用 emplace 方法

到目前为止,我们看到的是 emplace 方法改善性能的情况。但它们也能让之前不可能的代码变得可行,例如在容器中存储 std::unique_ptr 这样的只可移动类型。考虑这段代码:

1
std::vector<std::unique_ptr<Foo>> v1;

你会如何向这个 vector 插入值?一种方式是使用 push_back(),并直接在它的实参中构造值:

1
v1.push_back(std::unique_ptr<Foo>(new Foo(1, 2)));

这种语法能工作,但有点笨重。遗憾的是,绕开这种困惑的传统方式充满复杂性:

1
2
Foo *f2 = new Foo(1, 2);
v1.push_back(std::unique_ptr<Foo>(f2));

这段代码可以编译,但在插入之前,原始指针的所有权并不清晰。更糟的是,vector 现在拥有这个对象,但 f2 仍然有效,之后可能被意外删除。对不了解情况的读者来说,这种所有权模式可能很困惑,尤其是构造和插入不像上面这样连续发生时。

其他方案甚至无法编译,因为 unique_ptr 不可拷贝:

1
2
3
std::unique_ptr<Foo> f(new Foo(1, 2));
v1.push_back(f);             // 无法编译!
v1.push_back(new Foo(1, 2)); // 无法编译!

使用 emplace 方法,可以让你在创建对象的同时插入它,这更直观。在其他情况下,如果你需要把 unique_ptr 移入 vector,也可以这样做:

1
2
3
std::unique_ptr<Foo> f(new Foo(1, 2));
v1.emplace_back(new Foo(1, 2));
v1.push_back(std::move(f));

把 emplace 和标准迭代器结合起来,也可以在 vector 的任意位置插入对象:

1
v1.emplace(v1.begin(), new Foo(1, 2));

不过,从实践角度看,我们并不希望看到这些构造 unique_ptr 的方式。请使用 std::make_unique(C++14 起)或 absl::make_unique(如果你仍在 C++11)。

结论

本技巧中我们用 vector 做例子,但 emplace 方法也适用于 map、list 和其他 STL 容器。和 unique_ptr 结合使用时,emplace 能带来良好封装,并让堆分配对象的所有权语义以前所未有的方式变得清晰。希望这能让你感受到容器方法中新 emplace 家族的力量,并愿意在自己的代码中恰当地使用它们。

每周技巧 #64:原始字符串字面量

上一节

每周技巧 #74:委托构造函数和继承构造函数

下一节