C++开发初级
1057 浏览 5 years, 10 months
3.1.11 移动拷贝和移动赋值
版权声明: 转载请注明出处 http://www.codingsoho.com/移动拷贝和移动赋值
右值和右值引用
什么是右值?不能取地址的都是右值,包括即将消失的变量,常量等;能取地址的都是左值
C++中的引用必须绑定到1个左值,无法定义1个常量、表达式的引用
void fun(int& a);
int b=2, c=4;
fun(b); // CORRECT
fun(b+c); // WRONG
函数参数修改为const int&后,可以传递参数, 但无法修改该参数。
下面是几个常见符号
- & 左值引用
- && 右值引用
- *& 指针类型变量的引用
- &* 错误,不会用指针指向引用,没有引用类型的指针
常见的左值右值的赋值方式
int & ref1 = 5; // WRONG, 5 is rvalue
int & ref1 = x; // CORRECT
const int & ref2 =5; // CORRECT
const int & ref2 =y; // CORRECT
// const & 是万能引用,匹配左值和右值
int & ref3 = z;
ref3 = ref1; // 相当于 z=x 赋值,引用本身不能修改
move
举一个常见的swap的例子
常见的是实现方法如下:
swap(int a, int b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
int x=1; y=2;
swap(x,y);
swap(1,2);
上面的代码执行效率较低,进行了多次内容赋值
当然,也可以通过指针来实现
swap(int* a, int* b)
{
int tmp;
tmp = *a;
*a = *b;
b = *tmp;
}
int x=1; y=2;
swap(&x,&y);
在前面的调用中swap(1,2)
是没有意义的,两个常量不会交换,我们可以用下面两个函数进行左值和右值的区分
swap(const int& a, const int& b) // 接收右值
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
int x=1; y=2;
swap(x,y); // CORRECT
swap(1,2); // WRONG, value changed in function
swap(int& a, int& b) // 接收左值
{
int tmp;
tmp = std.move(a);
a = std.move(b);
b = std.move(tmp);
}
通过move
操作,减少了复制的过程
move的动机 - 复制的高昂代价
当对象非常庞大,如array数组类,复制构造1个数组的开销非差大。复制1个临时数组时,可以考虑使用移动的策略,将临时数组的“内部数据”直接移动到目标对象,而不是重新构建目标对象。 移动复制是一种破坏性复制,源对象的数据和状态被转移到目标对象,复制后,源对象不再有效。
假设有一个一维数组Array1D
#include <iostream>
#include<vector>
using namespace std;
class Array1D
{
public:
Array1D(int inSize);
Array1D(const Array1D& stc);
Array1D& operator=(const Array1D& rhs);
~Array1D();
protected:
int mSize;
char* pData;
};
Array1D::Array1D(int inSize)
{
static int count = 0;
mSize = inSize;
pData = new char[mSize];
count++;
cout << "Array1D(int inSize) was called " << count << " times!" << endl;
}
Array1D::~Array1D()
{
delete [] pData;
cout << "~Array1D() was called " << endl;
}
Array1D::Array1D(const Array1D& src)
{
static int count = 0;
mSize = src.mSize;
pData = new char[mSize];
memcpy(pData, src.pData, mSize);
count++;
cout << "Array1D(const Array1D& src) was called " << count << " times!" << endl;
}
Array1D& Array1D::operator=(const Array1D& rhs)
{
static int count = 0;
count++;
cout << "Array1D::operator=(const Array1D& rhs) was called " << count << " times!" << endl;
if(this == &rhs) return *this;
delete [] pData ;
// ...copy
mSize = rhs.mSize;
pData = new char[mSize];
memcpy(pData, rhs.pData, mSize);
return *this;
}
执行下面操作,ff()函数内构造Array1D对象通过temp返回,然后在s的复制构造函数中传给s. 注:temp的生命周期在Array1D s=ff();
这句中仍然有效
Array1D ff()
{
Array1D temp(100);
return temp;
}
Array1D s=ff();
虽然随着ff的返回,temp已经没有用处, 但仍然执行了内存分配与释放过程。
实际运行时并没有2次分配和释放,应该是编译器做了优化
移动的目标:让s的pData直接指向temp原来的内存,“接管”其内部数据,temp中的pData不再有效。
class Array1D
{
public:
Array1D(int inSize);
Array1D(const Array1D& src); //拷贝构造
Array1D(Array1D&& src); //移动构造
Array1D& operator=(const Array1D& rhs); //拷贝赋值
Array1D& operator=(Array1D&& rhs); //移动赋值
~Array1D();
protected:
int mSize;
char* pData;
};
如果要增加右值引用
和move语义
,在原来正常拷贝构造 与拷贝赋值的基础上,增加移动构造和移动赋值方法。
编译器根据上下文环境,选择不同的函数。
Array1D::Array1D(Array1D&& src)
{
static int count = 0;
mSize = src.mSize;
pData = src.pData;
src.mSize = 0;
src.pData = nullptr;
count++;
cout << "Array1D move constructor was called " << count << " times!" << endl;
}
移动时直接接管参数对象的数据,然后将其成员置空。
移动赋值运算符函数实现
Array1D& Array1D::operator=(Array1D&& lhs)
{
static int count = 0;
count++;
cout << "Array1D move assignment operator was called " << count << " times!" << endl;
if(this == &lhs) return *this;
delete [] pData ;
// ...move
mSize = lhs.mSize;
pData = lhs.pData;
lhs.mSize=0;
lhs.pData=nullptr;
return *this;
}
需要编写移动构造函数的类,往往也需要提供移动赋值 运算符,实现赋值时的移动。
普通拷贝和移动的示例
Array1D ff()
{
Array1D temp(100);
return temp;
}
Array1D a(100);
Array1D b(a); // 普通拷贝构造,需要内存分配
b=ff(); // 赋值运算
给b赋值的对象为左值,temp数组内部的数据将被移动,赋值给对象b。
强制移动的示例
Array1D a(100);
Array1D b=move(a);
通过move操作,强制将a移动并移动构造对象b 移动后,a对象不再有效,其数据已经被移走。
完整代码及输出如下
int main(){
Array1D a(100); // Array1D constructor was called 1 times!
Array1D b(a); // Array1D copy constructor was called 1 times!
b = ff(); // Array1D constructor was called 2 times!
// Array1D move assignment operator was called 1 times! 传递的是右值,所以调用移动赋值
//
Array1D am(100); // Array1D constructor was called 3 times!
Array1D bm=move(am);// Array1D move constructor was called 1 times! temp数组内部的数据将被移动,赋值给对象b。
b = move(bm); // Array1D move assignment operator was called 2 times!
return 0;
}
// ~Array1D() destructor was called
// ~Array1D() destructor was called
// ~Array1D() destructor was called
// ~Array1D() destructor was called
代码取自 Move\Move.cpp