C++左值&右值,左值引用&右值引用
200 Words | Read in about 1 Min | View times
Overview
C++11引入了右值引用的概念,用以区分C++11之前的引用(统称左值引用)。那么什么是右值引用?右值又是什么?和左值、左值引用有什么关系和区别?本节内容将一一解答。
本系列文章将包括以下领域:
本章其他内容请见 《现代C++》
expression表达式
要搞清楚左值和右值的概念,首先需要了解表达式的概念。
表达式,expression,是指由运算符(operator)和运算对象(operand)构成的计算式。字面值(literal)和变量(variable)是最基本的表达式,函数返回值也是表达式。表达式是可以求值的,对表达式求值将得到一个结果,这个结果包含两个属性——类型(type)和值类别(value categoties)。
在现代C++中,一个表达式的值类型可以划分为:泛左值(glvalue
)和右值(rvalue
)。泛左值又可以划分为左值(lvalue
)和将亡值(xvalue
),右值又可以划分为将亡值(xvalue
)和纯右值(prvalue
)。它们的关系可以用一张图来表示:
glvalue
= lvalue
+ xvalue
rvalue
= prvalue
+ xvalue
你肯定一脸疑惑,我们接下来逐一展开细说。对于左值、将亡值、纯右值其实只是表达式的属性,主要体现在使用上,比如能否取地址、能否使用移动语义等。
lvalue左值
lvalue
名为左值,是locator value
的缩写,并不是left value
,这点要特别强调。所以从字面意思也能看出,lvalue
是表示对象位置之物,在表达式结束后依然持有对象地址,可以起到定位的作用。
-
左值最重要的特点是可以取地址,这是区分右值的唯一标志。
-
可修改的左值可以是赋值操作符的左操作数,但不是绝对,等号左边的值不一定是左值。
-
所有具名对象或变量都是左值。
常见的左值表达式有以下类型:
-
具名变量、函数指针,如
std::cin
、std::cout
-
返回左值引用的函数调用
-
赋值或组合赋值类型,如
a = b
、a += b
-
前置自增或前置自减,如
++i
、--i
-
解引用表达式,如
*p
-
逗号表达式的最右的值是左值,如
a, b
,其中b
是左值 -
条件表达式,如
a ? b : c
,其中b
和c
是左值 -
字符串字面值:
"Hello World"
-
引用的强制转型表达式:
static_cast<int&>(x);
-
右值引用强转函数类型:
staic_cast<void(&&)(int)>(x);
总之,左值一定可以取地址,有名字的变量一定是左值(即使是void foo(T&& x)
中的x
也是左值,因为右值引用是有名字的,故右值引用也是左值)。等号左边的不一定是左值,因为=
运算符是可以重载的,完全可以使等号左边重载为右值的形式。
prvalue纯右值
prvalue
是pure rvalue
的缩写,纯粹的字面值(如25
、true
),或者求值结果是字面值或不具名的临时对象时,是一个纯右值。
-
纯右值不能是多态类型
-
非
class
的类型和非数组的纯右值不能被const
和volatile
修饰。 -
纯右值不能是不完整类型
常见的纯右值表达式有以下类型:
-
除字符串字面值外的其他字面值量,如
nullptr
、true
、false
-
返回非引用类型的函数调用
-
后置自增或后置自减,如
i++
、i--
-
算数表达式,如
a + b
、a % b
、a & b
-
逻辑表达式,如
a && b
、~a
-
比较表达式,如
a < b
、a >= b
-
取址表达式,如
&a
,其结果相当于一个unsigned int
类型的字面值 -
逗号表达式的最右的值是右值,如
a, b
,其中b
是右值 -
this
指针,因为this
也是一个地址 -
非引用的强制转型表达式:
static_cast<int>(x);
、(int)42
-
lambda表达式:
[](int x) { return x * x; };
xvalue将亡值
在C++11之前,右值和C++11的纯右值是等价的;而将亡值是伴随右值引用加入的概念。将亡值是由右值引用产生而引起的。
用左值去初始化或赋值给一个对象时,会调用拷贝构造函数或拷贝赋值运算符;而用右值去初始化或赋值给一个对象时,会调用移动构造函数或移动赋值运算符。当该右值完成初始化或赋值后,它的资源已经被移动给了被初始化或被赋值的对象上,该右值马上会被析构销毁,即“将亡”。
常见的将亡值表达式有以下类型:
-
返回右值引用的函数调用,如
std::move(x)
-
转换为右值引用的转换函数调用,如
static_cast<T&&>(x)
以上两种情况的函数调用返回值都是不具名的右值引用,它们属于右值,又因为都与C++11新加入的移动语义相关,故被这类右值称为“将亡值”。xvalue
与prvalue
在功能上及其类似,都不能作作为操作符的左操作数,都可以使用移动构造和移动赋值。
具名的右值引用是左值,不具名的右值引用是右值。
glvalue泛左值
泛左值要么是左值,要么是将亡值。具有以下特点:
-
glvalue
可以隐式地转换为纯右值:lvalue->rvalue的隐式转换、数组到指针的隐式转换、函数到指针的隐式转换 -
glvalue
可以是多态类型的 -
glvalue
可以是不完整类型
rvalue右值
右值要么是纯右值,要么是将亡值。rvalue
,是read value
的缩写,并不是right value
,这点要特别强调。具有以下特点:
-
rvalue
不可取地址 -
rvalue
不可用于赋值运算符或组合赋值的左操作数 -
rvalue
可以用来初始化const
左值引用,这样rvalue
的生命周期将被延长至该作用域结束 -
rvalue
可以用来初始化一个右值引用,这样rvalue
的生命周起也被延长至该作用域结束 -
如果存在一个函数的两个重载(只有某个参数不同,且这个参数在一个重载中是右值引用,另一个是
const
左值引用),此时用右值调用这个重载函数,将会匹配右值引用的函数
特殊情况
-
void
表达式,函数返回值是void
、强制类型转化为void
、throw
异常都被归类为纯右值,但是它们不可以用来初始化const
左值引用或右值引用,也不能作用函数的参数。 -
位域(bit field)是左值,但是位域不能取地址,也不能将一个非
const
引用绑定到位域上。但是const
引用可以绑定到位域上,此时会产生一个临时变量。
左值引用和右值引用
顾名思义,左值引用,是只能用来绑定左值的引用;右值引用,是只能用来绑定右值的引用。但const
左值引用既可以绑定非const
左值,又可以绑定const
左值,还可以绑定右值。
右值引用往往是将不具名的变量取别名,这样一来该右值引用具名了,变成了一个左值。所以右值引用不一定是右值,也可以是左值。请看以下例子:
1foo(std::move(x)); //std::move的返回值是一个右值引用,因为不具名,所以是个右值
2
3int&& temp = std::move(x); //std::move的返回值是一个右值引用,但该右值引用有名字temp,所以是一个左值
右值引用是用来支持转移语义的。转移语义可以将资源如堆、系统对象等从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高C++应用程序的性能。在后面的章节中我们将进一步讨论C++的移动语义。