Tip of the Week #65: Putting Things in their Place
Originally posted as totw/65 on 2013-12-12 By Hyrum Wright (hyrum@hyrumwright.org) “Let me ’splain. No, there is too much. Let me sum up.” –Inigo Montoya
C++11
中添加了一种新方式往标准容器中插入元素,那就是emplace()
系列方法了。这些方法会直接在容器中创建对象,避免创建临时对象,然后通过拷贝或者移动对象到容器中。这对于几乎所有的对象来说避免了拷贝,更加高效。尤其是对于往标准容器中存储只能移动的对象(例如std::unique_ptr
)就更为方便了。
The Old Way and the New Way
让我们通过使用vector
来存储的一个简单的例子来对比下两种方式。第一个例子是C++11之前的编码风格:
1 2 3 4 5 6 7 8 9 10 |
|
push_back
方法,会导致Foo对象被构造两次,一次是临时构造一个Foo对象,然后将临时对象进行移动构造,放到容器中。
我们可以使用C++11
引入的emplace_back()
,这种方式只会引入一个对象的构造,是直接在vector
容器元素所在内存上构造。正是由于emplace
系列函数将其参数直接转发给底层对象的构造函数,因此我们可以直接提供构造函数参数,从而无需创建临时的Foo
对象。
1 2 3 4 |
|
Using Emplace Methods for Move-Only Operations
到目前为止,我们已经研究过emplace
方法可以提高性能的情况,此外它可以让之前不能工作的代码可以正常工作,例如容器中的类型是只能被移动的类型像std::unique_ptr
。考虑下面这段代码:
1 |
|
push_back
直接在参数中构造对象:
1 |
|
1 2 |
|
vector
拥有了该对象,但是f2
仍然有效,并且有可能在此后被删除。对于不知情的读者来说,这种所有权模式可能会令人困惑,特别是如果构造和插入不是如上所述的顺序事件。
其他的解决方案甚至都无法编译,因为unique_ptr
是不能被拷贝的:
1 2 3 |
|
emplace
方法会使得对象的创建更为直观,如果你需要把unique_ptr
放到vector
容器中,可以像下面这样通过std::move
来完成:
1 2 3 |
|
通过把emplace
和标准的迭代器结合,可以将对象插入到vector
容器中的任何位置:
1 |
|
std::make_unique
(C++14),或者是absl::make_unique
(C++11)。
Conclusion
本文使用vector
来作为example中的标准容器,实际上emplace
同样也适用于map
、list
以及其它的STL容器。当unique_ptr
和emplace
结合,使得在堆上分配的对象其所有权的语义更加清晰。希望通过本文能让您感受到新的容器方法的强大功能,以及满足在您自己的代码中适当使用它们的愿望。