左值与右值

左值的字面理解就是可以出现在赋值运算符=左边的值(常量是左值,但它只有在初始化的时候在赋值运算符左边),非左值就是右值。

更准确的理解:

  • 左值有身份。可以是变量名、引用、指针,这样我们可以判断两个左值是相等、左值是否改变;

  • 右值可移动。允许把值移动到其他某处,自己处于合法但未指定状态,例如移动构造函数和移动赋值运算符对右值的操作,可以看我之前的文章

使用std::move可以将左值转为右值(rvalue_cast),使其变为可移动。

左值引用和右值引用

引用

一个引用是一个对象的别名,它的作用和指针类似,可以避免拷贝大的对象,通过操作引用或指针就能读取对象的数据。

引用与指针有本质的区别:

  • 虽然引用可能就是用常量指针实现的,但引用不是一个对象,我们无法指代一个引用,所以没有指向引用的指针,也没有元素是引用的数组。

  • 引用所引的对象永远是初始化时候指定的,不能再修改,而指针所指的对象是可以后续修改的;

  • 不存在空引用,引用必然有一个所引用的对象。指针可以为空指针。

左值引用

对于左值引用,必须区分普通变量引用和常量引用:

  • 普通T类型引用T&所引用的必须是T类型的左值;

  • 常量引用const T&所引用的可以是T类型(或者其他类型)的右值,此时:

    1. 执行T类型的隐式转换;
    2. 将该转换的右值存放在一个T类型的临时变量中;
    3. 把这个临时变量作为该常量引用的初始值。 例如,声明表达式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)...));
}

参考文献