前言
如果你还不知道C++11引入的右值、移动语义、完美转发是什么,可以阅读这篇文章;如果你已经对这些知识了如指掌,也可以看看有什么可以补充~😏
一、右值
值类别vs变量类型
在正式认识右值之前,我们要先区分值的类别和变量类型:
- 值 (value) 和 变量 (variable) 是两个独立的概念。值不一定拥有变量名(如表达式:i + j + k)。
- 值只有类别(category) 之分,而变量只有类型(type)之分。
值类别可以被划分左值和右值。
那什么是左值和右值呢?左值是能被取地址、不能被移动的值。右值是表达式中间结果/函数返回值(可能拥有变量名,也可能没有)。
有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值。
C++11扩展了右值的概念,将右值分为了纯右值和将亡值,但本文不作讨论。
如下的示例将帮助我们区分左值和右值:
int i = 3; // i是左值,3是右值
int j = i+8; // j是左值,i+8是右值
char a = getCh(); // a是左值 ,getCh()的返回值是右值(临时变量)
左值引用、右值引用、常引用
在以前的文章中,我们曾经讨论过左值引用和常引用的区别。在本篇文章中,我们需要进一步系统的了解它们三者之间的关系。
引用类型 可以分为两种:
- 左值引用:用
&
符号引用左值(但不能引用右值), - 右值引用:用
&&
符号引用右值(可以移动左值)。
在C++11中,因为增加了右值引用(rvalue reference)的概念,所以C++98中的引用都称为了左值引用(lvalue reference)。
使用方法如下所示:
int&& a = 3; // 3是右值,a是右值引用
int b = 8; // b是左值
int& bb = b; //bb是左值引用
int&& c = b + 5; // b+5是右值,c是右值引用
AA&& aa = getTemp(); // getTemp()的返回值是右值(临时变量)
左值引用十分常见,我们知道是给变量取个别名,但是引入右值引用的意义是什么呢?(将在下文中解答)
在上述的代码中,getTemp()的返回值本来在表达式语句结束后,其生命也就该终结了(因为是临时变量),而通过右值引用重获了新生,其生命周期将与右值引用类型变量aa的生命周期一样,只要aa还活着,该右值临时变量将会一直存活下去。
在下面的代码中将帮助我们区分左值引用和右值引用:
void func(T& a);//1,参数是左值
void func(T&& a);//2,参数是右值
//T类型的变量
T var;
T& rvar1 = var;//正确,rvar1是左值
T& rvar1 = T{
};//错误,左值引用不能引用右值
T&& rvar2 = T{
};//正确,rvar2是右值
T&& rvar2 = var;//错误,右值引用不能引用左值
T&& rvar2 = std::move(var);//正确,可以通过std::move()将左值转为右值引用
func(var);//进入1,a是左值
func(T{
});//进入2,a是右值
func(rvar1);//进入1,a是左值
func(rvar2);//进入1,rvar2是右值引用但a是左值
可以看出:
- 当左值引用变量
rvar1
在初始化时,不能绑定右值T{}
, - 当右值引用变量
rvar2
在初始化时,不能绑定左值var
,但是可以通过std