每周小贴士#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:就地安放的主要内容,如果未能解决你的问题,请参考以下文章