右值引用
左值和右值
- 左值不是left value,右值也不是right value
- 左值(lvalue) 是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据
- 右值(rvalue)译为 “read value”,指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)
- 判断左值和右值的方法:
- 可以位于等号左边的值为左值,反之不能位于等号左边的值称为右值
- 有名称、可以获得到地址的的表达式即为左值,反之为右值
右值引用
- 左值引用如
int &
不能引用右值,但加上const int &
可以引用右值,但不能修改 - 右值引用不能引用左值,即使加上const也不行
- 无论左值引用还是右值引用都必须在定义时初始化,且只能初始化一次(从一而终)
int x = 10;
int & y = x; //左值引用
int && z = 10; //右值引用
int & w = 10; //错误的
const int & ww = 10; //正确的,但不能修改值
int && x = 10;
x = 100; //没问题
const int && x = 10; //常量右值引用
x = 100; //错误的
- 无论是左值引用还时右值引用,在不加const限定符时,可以修改,否则不能修改
- 右值虽然无法获取地址,但右值引用是可以获取地址的,该地址表示临时对象的存储位置
- 通过右值引用的声明使右值“获得新生”,其生命周期与右值引用类型变量的生命周期一样长,只要右值引用变量还活着,该右值临时变量将会一直活下去
移动语义
1.C++11引入了 移动语义:实际文件还留在原来的地方,只修改记录,移动语义避免了移动原始数据
2. 编译器是通过引用的类型(左值引用和右值引用),来决定是否需要进行复制:
(1)可定义两个函数,其中一个是常规的拷贝构造函数,它使用const左值引用作为参数,拷贝构造函数执行深复制
(2)另一个函数时移动构造函数,它使用右值引用作为参数,只调整记录,将所有权转移给新对象的过程中,移动构造函数可能修改其实参,因此移动构造函数的右值引用参数不应为const
3. 移动构造函数的基本用法:
class A
{
public:
A(char* ptr) :m_p(ptr) { cout << "A(char * ptr)" << endl; }
A(const A& obj)
{
cout << "A(const A & obj)" << endl;
int len = strlen(obj.m_p);
m_p = new char[len + 1];
strcpy_s(m_p, len+1, obj.m_p);
}
A(A&& obj)//移动构造函数
{
cout << "A(A && obj)" << endl;
m_p = obj.m_p;
obj.m_p = nullptr;
}
~A()
{
cout << "~A()" << endl;
delete[] m_p;
}
private:
char* m_p;
};
A func()
{
char* ptr = new char[10]{ "hello" };
A a1(ptr);
return a1;
}
int main()
{
A a2 = func(); //将调用移动构造函数,因为func()返回的是右值不是左值
return 0;
}
- 适用于构造函数的移动语义也适用于赋值运算符,即移动赋值运算符
- 和移动构造函数相同,移动赋值运算符中的右值引用参数也不能为const
A & operator=(A && obj){
if(this == &obj)
return *this;
m_p = obj.m_p;
obj.m_p = nullptr;
return *this;
}
- 若想让左值使用移动构造函数和移动赋值运算符有两种途径
(1)使用static_cast<>将对象的类型强制转换为A&&
(2)C++11提供了std::move(),在utility头文件中
A a2 = static_cast<A&&>(a1); //两种方式任选一种均可
A a3 = std::move(a1);