右值引用
本节阅读量:前面我们引入了值范畴的概念(左值和右值)。值范畴是表达式的一种属性,有助于确定表达式会解析为值、函数还是对象。我们还引入了左值和右值,以便讨论左值引用。
左值引用概述
在C++11之前,C++中只有一种引用类型,因此它被称为“引用”。到了C++11,它被称为左值引用。左值引用只能用可修改的左值初始化。
| 左值引用 | 可以被初始化 | 可以被赋值 |
|---|---|---|
| 可修改的左值 | 是 | 是 |
| 不可修改的左值 | 否 | 否 |
| 右值 | 否 | 否 |
上面表格第一列表示左值引用想要引用的值。下面的例子对应第二行。
|
|
常量对象的左值引用可以用可修改左值、不可修改左值和右值进行初始化。然而,不能通过该引用修改这些值。
| const左值引用 | 可以被初始化 | 可以被赋值 |
|---|---|---|
| 可修改的左值 | 是 | 否 |
| 不可修改的左值 | 是 | 否 |
| 右值 | 是 | 否 |
常量对象的左值引用特别有用,因为它允许我们将任何类型的参数(左值或右值)传递给函数,而无需复制参数。
右值引用
C++11添加了一种新的引用类型,称为右值引用。右值引用被设计为(仅)使用右值初始化。左值引用使用单个与号创建,右值引用则使用双与号创建:
|
|
右值引用不能用左值初始化。
| 右值引用 | 可以被初始化 | 可以被赋值 |
|---|---|---|
| 可修改的左值 | 否 | 否 |
| 不可修改的左值 | 否 | 否 |
| 右值 | 是 | 是 |
| const右值引用 | 可以被初始化 | 可以被赋值 |
|---|---|---|
| 可修改的左值 | 否 | 否 |
| 不可修改的左值 | 否 | 否 |
| 右值 | 是 | 否 |
右值引用有两个有用属性。首先,右值引用会将其初始化对象的生命周期延长到右值引用的生命周期(常量对象的左值引用也可以这样做)。其次,非常量右值引用允许您修改右值!
让我们看一些例子:
|
|
该程序打印:
|
|
作为匿名对象,Fraction(3,5)通常会在定义它的表达式末尾超出作用域。然而,由于我们用它初始化右值引用,因此它的生命周期被延长到代码块末尾。随后,我们可以使用该右值引用来打印Fraction的值。
现在,让我们看一个不太直观的示例:
|
|
该程序打印:
|
|
虽然用字面值初始化右值引用,然后还能更改该值看起来很奇怪,但当用字面值初始化右值引用时,会先从字面值构造一个临时对象,因此引用的是这个临时对象,而不是字面值本身。
当然,右值引用通常不会以上述任何一种方式使用。
右值引用作为函数参数
右值引用通常用作函数参数。当您希望左值参数和右值参数具有不同行为时,它对函数重载最有用。
|
|
这将打印:
|
|
可以看到,当传递左值时,重载解析会选择左值引用版本;当传递右值时,重载解析会选择右值引用版本。
为什么要这么做?我们将在下一课中更详细地讨论这一点。可以先记住,它是移动语义的重要组成部分。
右值引用变量是左值
请考虑以下代码:
|
|
您希望上面的函数调用哪个版本的fun:fun(const int&)还是fun(int&&)?
答案可能会让你吃惊。它调用的是fun(const int&)。
尽管变量ref的类型是int&&,但在表达式中使用时,它是一个左值(与所有命名变量一样)。对象的类型和它的值类别是相互独立的。
您已经知道,字面值5是int类型的右值,而int x;是int类型的左值。类似地,int&& ref;是int&&类型的左值。
因此,fun(ref)不仅会调用fun(const int&),它甚至不会匹配fun(int&&),因为右值引用不能绑定到左值。
返回右值引用
您几乎不应该返回右值引用,原因和几乎不应该返回左值引用一样。在大多数情况下,被引用对象会在函数末尾超出作用域,从而留下悬空引用。