环境
VS2017,C++14
push_back应该是emplace_back的子集
不管是左值还是右值,push_back都调用的emplace_back。所以平时就用emplace_back也没毛病。
多变量的时候,只能是emplace_back
emplace版本会先进行分配内存,然后用placement new在给定的地址上进行调用构造函数
struct X {
int i;
int j;
public:
X()// 默认构造函数
{
std::cout << "X()" << std::endl;
}
X(int _i, int _j)// 默认构造函数
{
i = _i;
j = _j;
std::cout << "X(int _i, int _j)" << std::endl;
}
X(const X& rhs)// 复制构造函数
{
i = rhs.i;
j = rhs.j;
std::cout << "X(const X& rhs)" << std::endl;
}
~X()// 析构函数
{
std::cout << "~X()" << std::endl;
std::cout << this << std::endl;
}
X& operator=(const X& rhs) // 复制赋值运算符
{
i = rhs.i;
j = rhs.j;
std::cout << "X& operator=(const X& rhs)" << std::endl;
return *this;
}
// C++11新增
X(X&& rhs) // 移动构造函数
{
i = rhs.i;
j = rhs.j;
std::cout << "X(X&& rhs)" << std::endl;
}
X& operator=(X&& rhs) // 移动赋值运算符
{
i = rhs.i;
j = rhs.j;
std::cout << "X& operator=(X&& rhs)" << std::endl;
}
};
int main()
{
std::vector<X> v0;
//X x1;
v0.emplace_back(1, 2);
return 1;
}
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\include\xmemory0
placement new,相当于
new (ptr) Object(1,1)
ptr是分配好的一个地址。
参考:
char buf[100];
new(buf) X(1,1);
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\include\xmemory0
push_back左值的时候
std::vector<X> v0;
X x1(1,2);
v0.push_back(x1);
return 1;
依然会走到:
调用的构造函数是左值的拷贝构造函数
X(const X& rhs)// 复制构造函数
{
i = rhs.i;
j = rhs.j;
std::cout << "X(const X& rhs)" << std::endl;
}
这个construct是个模板,可以接受可变参数列表,所以不管是左值、右值,还是多个参数,都直接forward给构造函数,所以真的威力十足。
push_back左值的时候
可以想象的出,如果push_back的是右值,最后会调用X的右值构造函数。
再看个含隐式转换的例子,看出区别了
#include <vector>
#include <iostream>
using namespace std;
class testDemo
{
public:
testDemo(int num):num(num){
std::cout << "调用构造函数" << endl;
}
testDemo(const testDemo& other) :num(other.num) {
std::cout << "调用拷贝构造函数" << endl;
}
testDemo(testDemo&& other) :num(other.num) {
std::cout << "调用移动构造函数" << endl;
}
private:
int num;
};
int main()
{
cout << "emplace_back:" << endl;
std::vector<testDemo> demo1;
demo1.emplace_back(2);
cout << "push_back:" << endl;
std::vector<testDemo> demo2;
demo2.push_back(2);
}
输出:
emplace_back:
调用构造函数
push_back:
调用构造函数
调用移动构造函数
结论
首先呢,push_back在处理左值和右值时,都是转发到emplace_back。
之前所说的push_back不如emplace_back效率高,应该是在下面的情况:
testDemo(int num):num(num){
std::cout << "调用构造函数" << endl;
}
demo2.push_back(2);
以及
X(int _i, int _j)// 默认构造函数
{
i = _i;
j = _j;
std::cout << "X(int _i, int _j)" << std::endl;
}
v0.emplace_back(1, 2);
由于没有使用explicit,在push_back(2)的时候,这里面包含了一个隐式转换,生成了一个临时的testDemo对象。这个是push_back和emplace_back不同的地方,其余的都相同。
这个时候,调用的是:
所以,会有两个步骤,一个是生成临时对象,一个是将这个临时对象move到了vector的末尾
在变参数的时候,emplace_back会先分配内存,然后用placement new在指定的地址处调用构造函数进行初始化。省去了生成临时对象这一步骤。
所以,如果是往vector里添加左值或者右值,push_back和emplace_back是一样的。如果想直接用参数,或者会带隐式转换的,那就一定要用emplace_back。
最后的结论,用emplace_back,准没错。
另外,还有一点。不管是push_back还是emplace_back,最后调用的都是构造函数(或者拷贝构造函数),因为是用的placement new,而不是operator = (...)