首先直接上代码。
#include <cstdio>
#include <vector>
struct Widget {
int i = 0;
Widget() { std::printf("%p: Widget()\n", (void*)this); }
~Widget() { std::printf("%p: ~Widget()\n", (void*)this); }
Widget(int i) : i(i) {
std::printf("%p: Widget(%d)\n", (void*)this, this->i);
}
Widget(const Widget& w) : i(w.i) {
std::printf("%p: Widget(const Widget&), i=%d\n", (void*)this, this->i);
}
Widget& operator=(const Widget& w) {
this->i = std::move(w.i); // NOLINT
std::printf("%p: Widget& =operator(const Widget&), %d\n", (void*)this,
this->i);
return *this;
}
Widget(Widget&& w) noexcept : i(w.i) {
std::printf("%p: Widget(Widget&&), i=%d\n", (void*)this, this->i);
}
Widget& operator=(Widget&& w) noexcept {
if (this == &w) {
return *this;
}
this->i = std::move(w.i); // NOLINT
std::printf("%p: Widget& =operator(Widget&&), i=%d\n", (void*)this,
this->i);
return *this;
}
};
int main() {
std::vector<Widget> V{};
V.reserve(16);
{
std::printf("\npush\n");
Widget W(Widget(3));
V.push_back(W);
}
{
std::printf("emplace\n");
Widget W(Widget(3));
V.emplace_back(W);
}
{
std::printf("\npush\n");
Widget W(Widget(3));
Widget &&WR = std::move(W);
V.push_back(WR);
}
{
std::printf("emplace\n");
Widget W(Widget(3));
Widget &&WR = std::move(W);
V.emplace_back(WR);
}
{
std::printf("\npush\n");
Widget W(Widget(4));
V.push_back(std::move(W));
}
{
std::printf("emplace\n");
Widget W(Widget(4));
V.emplace_back(std::move(W));
}
{
std::printf("\npush\n");
V.push_back(Widget(5)); // NOLINT
}
{
std::printf("emplace\n");
V.emplace_back(Widget(5)); // NOLINT
}
{
std::printf("\npush\n");
V.push_back({6});
}
{
std::printf("emplace\n");
V.emplace_back(6);
}
std::printf("\n---------\n");
输出结果如下:
push
0x7fff38706a40: Widget(3)
0x55be449b3820: Widget(const Widget&), i=3
0x7fff38706a40: ~Widget()
emplace
0x7fff38706a3c: Widget(3)
0x55be449b3824: Widget(const Widget&), i=3
0x7fff38706a3c: ~Widget()
push
0x7fff38706a38: Widget(3)
0x55be449b3828: Widget(const Widget&), i=3
0x7fff38706a38: ~Widget()
emplace
0x7fff38706a2c: Widget(3)
0x55be449b382c: Widget(const Widget&), i=3
0x7fff38706a2c: ~Widget()
push
0x7fff38706a1c: Widget(4)
0x55be449b3830: Widget(Widget&&), i=4
0x7fff38706a1c: ~Widget()
emplace
0x7fff38706a18: Widget(4)
0x55be449b3834: Widget(Widget&&), i=4
0x7fff38706a18: ~Widget()
push
0x7fff38706a14: Widget(5)
0x55be449b3838: Widget(Widget&&), i=5
0x7fff38706a14: ~Widget()
emplace
0x7fff38706a10: Widget(5)
0x55be449b383c: Widget(Widget&&), i=5
0x7fff38706a10: ~Widget()
push
0x7fff38706a0c: Widget(6)
0x55be449b3840: Widget(Widget&&), i=6
0x7fff38706a0c: ~Widget()
emplace
0x55be449b3844: Widget(6)
---------
0x55be449b3820: ~Widget()
0x55be449b3824: ~Widget()
0x55be449b3828: ~Widget()
0x55be449b382c: ~Widget()
0x55be449b3830: ~Widget()
0x55be449b3834: ~Widget()
0x55be449b3838: ~Widget()
0x55be449b383c: ~Widget()
0x55be449b3840: ~Widget()
0x55be449b3844: ~Widget()
从结果可以看出:
- 对于已有的
Widget,只要传入的是lvalue(包括上面的W或WR),push_back和emplace_back都调用的是copy构造函数。 - 对于可以move的表达式(即rvalue),push_back和emplace_back都调用的是move构造函数。
- emplace_back比push_back多了一个in-place的用法,这是emplace_back体现优越性的地方。这对于代码中最后的情形(传入6)。这里push_back使用了initializer_list来构造,这个用法和传入
Widget(6)是一样的(即和代码中传入5使用Widget(6)的方式一样);但是对于emplace_back则可以避开push_back中调用move构造函数并且析构之前生成的对象的过程。
因此,如果只考虑性能,用emplace_back来平替push_back就可以了。
不过使用emplace_back可能会有一些bug不能够被编译器所识别。如下面的使用:
template <typename T>
using Vec = std::vector<T>;
int main() {
{
Vec<Vec<int>> vec(1);
vec[0].push_back(1 << 20); // vec[0] == {2^20}
vec[0].emplace_back(1 << 20); // vec[0] == {2^20, 2^20}
}
{
Vec<Vec<int>> vec(1);
// vec.push_back(1 << 20); // 无法编译
vec.emplace_back(1 << 20); // 创建了大小为2^20的vector
vec.push_back(Vec<int>(1 << 20)); // 同上
}
}
由于emplace_back既可以初始化已有对象,也可以传入对象的构造函数来in-place构造对象。而vec.emplace_back(1 << 20)是in-place构造std::vector;由于std::vector<T>包含传入std::size_t的构造函数,因此出现了创建了大小为2^20的vector的错误。