版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yockie/article/details/79038780
在阅读之前可以可以先看这篇文章复习一下std::move与std::forward:
理解std::move和std::forward
背景
遇到以下场景,Bar类中有成员变量foo是Foo类的对象,在项目代码中需要申请一个Bar对象,给其中foo中的变量复制,并插入到vector< Bar >中,定义如下:
struct Foo {
std::string name;
std::string id;
Foo() {
std::cout<<"Foo construct"<<std::endl;
printf("%p\n", this);
}
~Foo() {
std::cout<<"Foo deconstruct------"<<std::endl;
printf("%p\n", this);
}
Foo(const Foo& f) {
std::cout<<"Foo copy construct"<<std::endl;
printf("%p\n", this);
}
Foo(Foo&& f) {
name = std::move(f.name);
id = std::move(f.id);
std::cout<<"Foo move construct"<<std::endl;
printf("%p\n", this);
}
Foo& operator= (Foo&& f) {
std::cout<<"Foo move operator"<<std::endl;
return *this;
}
};
struct Bar {
std::string* key;
Foo foo;
Bar(std::string* k, Foo&& f) :
key(k), foo(std::forward<Foo>(f)) {
std::cout<<"Bar move construct1"<<std::endl;
}
Bar(Bar && bar) {
std::cout<<"Bar move construct2"<<std::endl;
}
Bar(const Bar & bar) {
std::cout<<"Bar copy construct"<<std::endl;
}
Bar() : key(nullptr) {
std::cout<<"Bar default construct"<<std::endl;
}
Bar(std::string* k) : key(k) {
std::cout<<"Bar construct"<<std::endl;
}
~Bar() {
std::cout<<"Bar deconstruct***************"<<std::endl;
}
};
int main(int argc, const char * argv[]) {
std::vector<Bar> vec;
//预先准备的数据,不考虑优化这3个值的复制操作
std::string key = "key";
std::string name = "yockie";
std::string id = "616";
//下文仅修改此域代码:
{
//vec中插入一个Bar,使Bar的key为&key,foo的name、id分别为上述的name、id,如何尽量减少对象拷贝?
}
return 0;
}
问题:
上面代码中已经提出了,vec中插入一个Bar,使Bar的key为&key,foo的name、id分别为上述的name、id,如何尽量减少对象拷贝?
方法一:
最直观的方法:
{
Bar bar;
bar.key = &key;
bar.foo.name = name;
bar.foo.id = id;
vec.push_back(bar);
}
打印结果,并逐行解释:
//进入Bar(),先构造成员变量foo,所以进入了Foo(),所以打印了以下2行
Foo construct
0x7fff5fbff3a8
//成员变量构造完成,进入Bar()所以打印以下这行
Bar default construct
//push_back要构造新的Bar对象,进入Bar(const Bar & bar),同样先构造成员变量foo,所以进入了Foo(),所以打印了以下2行
Foo construct
0x100446c18
//成员变量构造完成,进入Bar(const Bar & bar)所以打印以下这行
Bar copy construct
//退出子域,释放Bar bar;对象,先进入Bar析构函数体:
Bar deconstruct***************
//再释放各成员变量内存
Foo deconstruct------
0x7fff5fbff3a8
//退出main函数,释放vector空间,先释放各元素空间,同样先进入Bar析构函数体:
Bar deconstruct***************
//再释放各成员变量内存
Foo deconstruct------
0x100446c18
方法二:
{
Bar bar;
bar.key = &key;
bar.foo.name = name;
bar.foo.id = id;
vec.push_back(std::move(bar));
}
打印结果,及差异部分的解释:
//以下3行同上相同
Foo construct
0x7fff5fbff390
Bar default construct
//std::move把bar从左值转为右值,进而调用Bar(Bar && bar)而不是Bar(const Bar & bar),先构造成员变量foo:
Foo construct
0x100701ac8
//再进入Bar(Bar && bar)函数体,这里构造的Bar对象是vector中是元素,所以后面释放两个bar对象,先释放子域中的bar对象
Bar move construct2
Bar deconstruct***************
Foo deconstruct------
0x7fff5fbff390
//再释放vector中的bar对象
Bar deconstruct***************
Foo deconstruct------
0x100701ac8
上述例子充分说明一点,std::move并不会”移动“内存,只是把左值转为右值,进而决定使用哪一种(这里是)构造函数,和方法一一样会构造新对象。
方法3:
{
Foo foo;
foo.name = name;
foo.id = id;
vec.emplace_back(&key, std::move(foo));
}
打印结果,及差异部分解释:
//子域中foo对象构造
Foo construct
0x7fff5fbff348
//emplace_back中的参数将调用Bar的隐式构造函数Bar(std::string* k, Foo&& f),后者的初始化列表调用std::forward将f从左值转为右值,进而选择调用foo的移动构造函数Foo(Foo&& f):
Foo move construct
0x100756c78
//再进入Bar(std::string* k, Foo&& f)函数体
Bar move construct1
//释放子域foo对象
Foo deconstruct------
0x7fff5fbff348
//释放vector成员内存,先进入析构函数体
Bar deconstruct***************
//再释放成员变量内存
Foo deconstruct------
0x100756c78
上述例子也充分说明一点,std::forward也不会”移动“内存,只是在特定条件下把左值转为右值,进而决定使用哪一种(这里是)构造函数,和方法一一样会构造新对象。
方法四:
{
vec.emplace_back(&key);
vec[0].foo.name = name;
vec[0].foo.id = name;
}
打印结果,及解释:
//emplace_back中的参数将调用Bar的隐式构造函数Bar(std::string* k),先构造成员函数:
Foo construct
0x10044e4c8
//进入Bar(std::string* k)函数体
Bar construct
//退出main函数前释放vector内存,先释放插入的元素的内存
Bar deconstruct***************
Foo deconstruct------
0x10044e4c8
方法四充分利用了emplace_back利用临时对象内存、不构造新对象的特点,Bar和Foo对象仅构造和释放了一次,已经相当优化了。