1、目的
右值引用,移动构造和移动赋值是在C++11中引入的,其目的是为了提升代码效率
2、使用场景
类中如果有需要申请动态资源的成员,那么定义移动构造函数和移动赋值运算符可以避免不必要的拷贝工作,从而提升代码效率。移动构造和移动赋值并不会新开辟资源,而是将源对象的一部分或全部资源移交给了新对象。std::move()的作用是将一个左值转换为右值,但它并没有进行真正的内存移动操作。
3、代码举例
class A
{
public:
A(int a):a_(a),vp(std::vector<int>(1000000))
{
for(int i=0;i<vp.size();i++)
{
vp[i] = i;
}
std::cout<<vp.size()<<std::endl;
}
A(A &&tmpA)//移动构造函数
{
if(this != &tmpA)
{
a_ = tmpA.a_;
std::cout<<&(tmpA.vp[0])<<std::endl;//这里打印了一下tmpA.vp[0]的地址是为了说明是是否发生了拷贝
vp.clear();
vp = std::move(tmpA.vp);
}
std::cout<<"移动构造函数"<<&(vp[0])<<" "<<vp.size()<<" "<<tmpA.vp.size()<<std::endl;
}
A& operator=(A &&tmpA)
{
if(this != &tmpA)
{
a_ = tmpA.a_;
vp.clear();
vp = std::move(tmpA.vp);
}
std::cout<<"移动赋值"<<std::endl;
return *this;
}
A(const A &tmpA)//拷贝构造函数
{
if(this != &tmpA)
{
a_ = tmpA.a_;
std::cout<<&(tmpA.vp[0])<<std::endl;
vp.clear();
vp.assign(tmpA.vp.begin(),tmpA.vp.end());
}
std::cout<<"拷贝构造函数"<<&(vp[0])<<" "<<vp.size()<<" "<<tmpA.vp.size()<<std::endl;
}
private:
int a_;
std::vector<int> vp;
};
int main()
{
std::chrono::steady_clock::time_point start_time;
std::chrono::steady_clock::time_point end_time;
A a1(10);
start_time = std::chrono::steady_clock::now();
A a2(a1);
end_time = std::chrono::steady_clock::now();
std::chrono::duration<double> diff = end_time-start_time;
std::cout<<"拷贝构造耗时:"<<diff.count()*1000<<"ms"<<std::endl;
start_time = std::chrono::steady_clock::now();
A a3(std::move(a1));
end_time = std::chrono::steady_clock::now();
diff = end_time-start_time;
std::cout<<"移动构造耗时:"<<diff.count()*1000<<"ms"<<std::endl;
return 0;
}
打印的log如下:
1000000
0x7fec1db5e010
拷贝构造函数0x7fec1d78d010 1000000 1000000
拷贝构造耗时:2.37644ms
0x7fec1db5e010
移动构造函数0x7fec1db5e010 1000000 0
移动构造耗时:0.004215ms
移动赋值
从log上看出拷贝构造函数发生了实际的内存拷贝,源对象依旧有效,新对象是在新的内存地址上的一个对象,只是内容与源对象相同。而移动构造函数新对象的vp成员接管了源对象的vp成员的内存,没有发生内存复制,从打印的vp[0]的地址可以看出新对象的vp[0]地址与源对象的vp[0]地址相同,说明没有复制内存,但移动构造后源对象中的vp.size()变成了0,说明源对象已经失去了那块内存所有权。
从打印的耗时上看,移动构造要比拷贝构造快很多,这和拷贝的数据量多少有关。
另外,调用移动构造函数后,不因该对源对象做任何假设,源对象不能再使用了。