【C++】左值、右值和引用
左值与右值
左值的字面理解就是可以出现在赋值运算符=
左边的值(常量是左值,但它只有在初始化的时候在赋值运算符左边),非左值就是右值。
更准确的理解:
-
左值有身份。可以是变量名、引用、指针,这样我们可以判断两个左值是相等、左值是否改变;
-
右值可移动。允许把值移动到其他某处,自己处于合法但未指定状态,例如移动构造函数和移动赋值运算符对右值的操作,可以看我之前的文章。
使用std::move
可以将左值转为右值(rvalue_cast),使其变为可移动。
左值引用和右值引用
引用
一个引用是一个对象的别名,它的作用和指针类似,可以避免拷贝大的对象,通过操作引用或指针就能读取对象的数据。
引用与指针有本质的区别:
-
虽然引用可能就是用常量指针实现的,但引用不是一个对象,我们无法指代一个引用,所以没有指向引用的指针,也没有元素是引用的数组。
-
引用所引的对象永远是初始化时候指定的,不能再修改,而指针所指的对象是可以后续修改的;
-
不存在空引用,引用必然有一个所引用的对象。指针可以为空指针。
左值引用
对于左值引用,必须区分普通变量引用和常量引用:
-
普通T类型引用
T&
所引用的必须是T类型的左值; -
常量引用
const T&
所引用的可以是T类型(或者其他类型)的右值,此时:- 执行T类型的隐式转换;
- 将该转换的右值存放在一个T类型的临时变量中;
- 把这个临时变量作为该常量引用的初始值。
例如,声明表达式
const double& cdr{1}
是合法的,可以这样理解:double temp = double{1}; const double& cdr{temp};
右值引用
右值引用实现了一种破坏性读取。它对应一个临时对象,用户可以修改(移动)这个对象,因为可以认为之后再也用不到这个对象了。
右值引用只能绑定右值,就像左值引用(非常量左值引用)只能绑定左值。当右值引用想绑定左值时,需要先用std::move
把左值转为右值。
通用引用
这个概念是模板元编程里面的。当函数的形参看似是一个右值引用时,它实际上也可能是左值引用,例如:
template <class T>
T func(T&& a) {
return a;
}
当传入左值引用时:
Matric a;
Matric& refa;
func(refa);
通过引用折叠,a就是一个左值引用而不是右值引用。通用引用的实际用途可以拿来做通用转发,例如std::make_unique
,可以这样实现:
template<class _Tp, class... _Args>
unique_ptr<_Tp>
make_unique(_Args&&... __args)
{
return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...));
}