现在来对比一下emplace_back
与push_back
的区别,分别对有移动构造函数和移动赋值运算符以及没有这两个函数的对象进行比较
1.没有 移动构造函数和移动赋值运算符
class Test
{
public:
Test()
{
qDebug() << "construct test" << this;
}
Test (const Test& other)
{
qDebug() << "copy construct test" << this;
}
Test& operator=(const Test& other)
{
qDebug() << "assgin operator" << this;
}
~Test()
{
qDebug() << "destruct test" << this;
}
};
std::vector<Test> v;
//测试1, push_back 放入 左值
Test t;
v.push_back(t); //调用拷贝构造
// 测试1, 结果:
construct 0x3c296f940
copy construct 0x15db0916660
destruct test 0x3c296f940
destruct test 0x15db0916660
测试1结果:
对没有
移动构造和移动赋值运算符的对象,push_back
放入一个左值
,是调用一次构造函数生成对象t,一次拷贝构造函数,两次析构函数
//测试2, push_back 放入右值,
Test t;
v.push_back(std::move(t)); //1.通过 move 将左值转换成 右值 //调用拷贝构造
// v.push_back(Test()); //2.构造临时对象为右值
//1 和 2 的两种右值,其实是一样的;
//第一种方式的 t对象,会在出了作用域之后析构
//第二种生成一个临时对象,这一行结束之后,临时对象立即析构
//在以下测试统一使用第一种方式生成右值
//测试2,结果:
construct 0xc8c64ffce0
copy construct 0x1dd8d28af00
destruct test 0xc8c64ffce0
destruct test 0x1dd8d28af00
测试2结果:
对没有
移动构造和移动赋值运算符的对象,push_back
放入一个右值
,是调用一次构造函数生成对象t,一次拷贝构造函数,两次析构函数
//测试3, emplace_back 放入 左值
Test t;
v.emplace_back(t); //调用拷贝构造
//测试3,结果:
construct 0xe60db8fd20
copy construct 0x2b7a4a35ab0
destruct test 0xe60db8fd20
destruct test 0x2b7a4a35ab0
测试3结果:
对没有
移动构造和移动赋值运算符的对象, emplace_back
放入一个左值
,是调用一次构造函数生成对象t,一次拷贝构造函数,两次析构函数
//测试4, emplace_back放入右值
Test t;
v.emplace_back(std::move(t)); //调用拷贝构造
//测试4,结果:
construct 0xf1004ff850
copy construct 0x2a4b16a79a0
destruct test 0xf1004ff850
destruct test 0x2a4b16a79a0
测试4结果:
对没有
移动构造和移动赋值运算符的对象, emplace_back
放入一个右值
,是调用一次构造函数生成对象t,一次拷贝构造函数,两次析构函数
2.有 移动构造函数和移动赋值运算符
class Test
{
public:
Test()
{
qDebug() << "construct" << this;
}
Test (const Test& other)
{
qDebug() << "copy construct" << this;
}
Test& operator=(const Test& other)
{
qDebug() << "assgin operator" << this;
}
Test (Test&& other)
{
qDebug() << "move construct" << this;
}
Test& operator=(Test&& other)
{
qDebug() << "move operator" << this;
}
~Test()
{
qDebug() << "destruct test" << this;
}
};
依次将上面的测试再进行一次:
//测试5, push_back 放入左值
Test t;
v.push_back(t); //调用拷贝构造
//测试5,结果
construct 0x73832ff720
copy construct 0x1b70034d390
destruct test 0x73832ff720
destruct test 0x1b70034d390
测试5结果:
对有
移动构造和移动赋值运算符的对象, push_back
放入一个左值
,是调用一次构造函数生成对象t,一次拷贝构造函数,两次析构函数
//测试6, push_back放入右值
Test t;
v.push_back(std::move(t)); //调用移动构造
//测试6,结果:
construct 0x7dd78ff770
move construct 0x243f1bc3f40
destruct test 0x7dd78ff770
destruct test 0x243f1bc3f40
测试6结果:
对有
移动构造和移动赋值运算符的对象,push_back
放入一个右值
,是调用一次构造函数生成对象t,一次移动构造函数
,两次析构函数
//测试7, emplace_back放入左值
Test t;
v.emplace_back(t); //调用拷贝构造
//测试7,结果:
construct 0xbc301bf7c0
copy construct 0x27f1caee380
destruct test 0xbc301bf7c0
destruct test 0x27f1caee380
测试7结果:
对有
移动构造和移动赋值运算符的对象,emplace_back
放入一个左值
,是调用一次构造函数生成对象t,一次拷贝构造函数,两次析构函数
//测试8, emplace_back放入右值
Test t;
v.emplace_back(std::move(t)); //调用移动构造
//测试8,结果:
construct 0x95bbb8f930
move construct 0x246db820f20
destruct test 0x95bbb8f930
destruct test 0x246db820f20
测试8结果:
对有
移动构造和移动赋值运算符的对象,emplace_back
放入一个右值
,是调用一次构造函数生成对象t,一次移动构造函数
,两次析构函数
结论:
1.没有移动构造和移动赋值运算符
左值 | 右值 | |
---|---|---|
push_back | 拷贝构造函数 | 拷贝构造函数 |
emplace_back | 拷贝构造函数 | 拷贝构造函数 |
2.有移动构造和移动赋值运算符
左值 | 右值 | |
---|---|---|
push_back | 拷贝构造函数 | 移动构造函数 |
emplace_back | 拷贝构造函数 | 移动构造函数 |
- 看起来,无论是有移动构造和移动赋值运算符的对象,还是没有这两个函数的对象,
emplace_back
与push_back
表现出来的特征是相同的, 并未出现任何性能上的改善与提升
正确使用:
emplace_back(Args...);
- 实际,
emplace_back
真正能带来效率上的提升的用法是:直接在emplace_back
里面传递参数原地调用构造函数,构造出一个对象,这样做至少可以节省一次拷贝构造(针对左值)
或者移动构造(针对右值)
带来的开销;而不是预先生成一个左值或右值类型的对象,然后再将其放入到emplace_back
的参数中,这样的做法与push_back
并无差异;