每周小贴士#65:就地安放

Posted 飞鹤0755

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每周小贴士#65:就地安放相关的知识,希望对你有一定的参考价值。

作为totw/65最初发表于2013年12月12日

由Hyrum Wright (hyrum@hyrumwright.org)创作

“让我解释一下。不,这太多了。让我总结一下。”——埃尼戈·蒙托亚

C++11添加了一种将元素插入标准容器的新方法:emplace()方法系列。这些方法直接在容器中创建对象,而不是创建一个临时的对象,然后拷贝或移动这个对象进容器。避免这些副本对于绝大多数对象而言都是更有效率的,并且在标准容器中存储只移动的对象(例如std::unqie_ptr)也会更容易。

旧方法和新方法

让我们来看一下使用相对两种方式使用vector的简单示例。第一个示例使用C++11之前的代码:

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:

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

对只移动操作使用Emplace方法。

到目前为止,我们已经看到了许多emplace方法提供性能的情形,但是它们也能使得之前不可能的代码变得可行,例如在容器中存储像std:unqie_ptr这种只能移动的类型。考虑这个片段:

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

你将如何插入值进入这种vector呢?一种方法是使用push_back,并且直接在它的参数中构造值:

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

这语法能够工作,但有点累赘。不幸的是,绕过这种混淆的传统方法充满了复杂性。

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

这段代码能够编译,但是在插入之前,它使得原生指针的所有权不清晰。更糟糕的是,vector现在拥有了些对象,但是f2依然保持有效,并且可能在以后被意外地删除。对于不知情的读者而言,这种所有权模式可能令人困惑,特别是如果构造和插入不是如上所述的连续事件。

其他解决方案甚至无法编译,因为unique_ptr不能复制。

std::unique_ptr<Foo> f(new Foo(1, 2));
v1.push_back(f);             // Does not compile!
v1.push_back(new Foo(1, 2)); // Does not compile!

使用emplace方法可以更直接地在创建对象时插入它。在其他情况下,如果你需要移动unique_ptr进入vector,你可以:

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

结合emplace和标准iterator,你也能够在vector任意位置插入对象:

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

也就是说,实际上我们不想看到这些构造unqiue_ptr的方法——使用std::make_unique(来自C++14)或absl::make_unique(如果你仍在使用C++11)。

结论

我们在本贴士中使用vector作为示例,但是emplace方法也可以用于map,list和其他STL容器。当与unqiue_ptr组合使用时,emplace能有更好的封装,并且使得堆分配对象所有权语义这种方式变得更清晰,这在以前是不可能的。希望这能让你感受到emplace系列容器方法的强大,并且希望在你的代码中适当地方使用它们。

以上是关于每周小贴士#65:就地安放的主要内容,如果未能解决你的问题,请参考以下文章

每周小贴士#152:AbslHashValue和你

每周小贴士#153:不要使用using指令

每周小贴士#148:重载集

每周小贴士#148:重载集

每周小贴士#158:Abseil关联窗口和contains()

每周小贴士#149:对象生命周期与=delete