真正理解std::move
std::move的作用只有一个,那就是把一个左值强制转换为右值,有了这个右值,右值允许的任何操作就可以实施了。比如:1. 这个右值可以赋给一个左值变量,2. 这个右值可以被一个右值引用来引用。
class A
{
public:
A(int n):m_size(n)
{
m_ptr=new int[n];
}
int m_size;
int* m_ptr;
~A()
{
cout<<" Destructor is called m_ptr="<<m_ptr<<endl;
delete[] m_ptr;
m_ptr=nullptr;
}
A(A& a)
{
cout<<" copy constructor is called"<<endl;
m_size=a.m_size;
m_ptr=new int[m_size];
}
A(A&& a) // move constructor
{
if(this!=&a)
{
m_ptr=a.m_ptr;
m_size=a.m_size;
a.m_ptr=nullptr;
}
cout<<" move constructor is called"<<endl;
}
A& operator=( A&&r) // move assignment operator
{
if(this!=&r)
{
delete[] m_ptr;
m_ptr=r.m_ptr;
m_size=r.m_size;
cout<<" move assignment operator is called"<<endl;
r.m_ptr=nullptr;
}
return *this;
}
};
int main(int argc, char const *argv[])
{
A a1(10); // a1 is a left value
cout<<"1---will use copy constructor below"<<endl;
A a2=a1; //这是定义一个左值a2,并且用另一个左值a1来赋值,拷贝构造函数被调用
cout<<"2---will use move constructor below"<<endl;
A a3=move(a1); // 移动构造函数被调用,在移动构造函数中,a3的m_ptr变量直接指向a1的m_ptr指向的内存,
// 这样,对象a3不需要重新分配内存,而是直接使用a1.m_ptr指向的内存。
// 于是a1.m_ptr指向的内存就被移动走了,逻辑上,a1.m_ptr指向必须为空了。
// 为啥多此一举呢,直接操作a1不就行了???????
cout<<"a1.m_ptr=="<<a1.m_ptr<<" 显示被移走了"<<endl;
//a1被move了之后,依然是个左值, so any operation appled to a left value will be applied to a1 as well.
// 下面我们给a1重新赋值
cout<<"3---will use move assignment operator below"<<endl;
a1=A(40); // 右值赋给一个已经定义的左值,于是移动赋值运算符被调用
cout<<"4-----旧的a1生命周期结束,这是旧的a1的析构函数被调用"<<endl;
// till now, there are 3 objects alive, a1(A(40)),a2,a3, 在程序退出后,这3个对象需要销毁,
// 于是析构函数被调用4次
cout<<"5--生命周期结束,销毁3个对象"<<endl;
return 0;
}
执行结果如下,仔细看一下吧
下面我详细说明一个std::swap的代码实现原理。
// std::swap函数的实现
template<class T>
void swap(T &x, T& y)
{
T temp = std::move(x);
x = std::move(y);
y = std::move(temp);
}
第一行代码:T temp = std::move(x); 调用类型T的移动构造函数,使得temp指向x的资源(包括temp的值等于x的值),这个时候x依然是个左值
第二行 代码:x = std::move(y); 让x指向y的资源(包括x等于y的值)
第三行代码:y = std::move(temp); 让y指向temp的资源(包括y等于temp的值,也就是最初的x的值)
执行结果如下:可以看到,执行swap函数后,a1和a2进行了交换。上面红色方框中的a1的值,与a2进行了交换。
在看一下swap函数内部的执行情况,黄色数字1,2,3,4
黄色数字1表示 执行T temp = std::move(x),这里移动构造函数被调用
黄色数字2表示 执行x = std::move(y);,这里移动赋值函数被调用
黄色数字3表示 执行y = std::move(temp),这里移动赋值函数被调用
黄色数字4表示函数退出的时候,临时变量temp需要销毁,于是析构函数被调用
执行结果的最后两个表示a1和a2被析构。
最后有几点需要说明:
1. std::move和移动构造函数、移动赋值运算符没有任何联系。
2.移动构造函数、移动赋值运算符的调用和一个右值赋值给一个左值的形式有关系。比如:
A a1(10);
A a3=move(a1)
上述情况在定义a3的同时,给赋一个右值,这时移动构造函数被触发。
如果是:
A a3(10);
a3=A(20);
这是一个右值被赋给一个已经创建成功的左值变量,很显然,这时移动赋值运算符被触发。
3. move也和右值引用没有任何联系,可以参考这篇博文:理解C++中的右值引用-CSDN博客
4. 一个左值被move之后,这个左值还能被使用吗?不能被使用,因为这个左值指向的资源可能都被“move”走了,比如:
A a1(10);
A a3=move(a1)
a1被move之后,它的指针已经为空指针了,所以操作a1,可能会得到不可预测的结果。但是a1可以被重新赋值,比如:
a1=A{100),之后a1就又是一个正常的左值了。