自上次撰写相关内容,我也算是有了一段实际的应用经验。现在,回头再次分析相关内容,有了更深的感悟。今儿个,我们再次探究一下左值、右值与引用等相关内容。
一、左值与右值的基本概念
1.1 什么是左值(lvalue)和右值(rvalue)
-
左值(lvalue):表示具有持久存储的对象,可以获取其地址。通常是命名变量、数组元素、类成员等,可以出现在赋值语句的左侧。
-
右值(rvalue):表示不具有持久存储的临时对象或值,通常是字面量、临时对象、表达式的结果等,不能获取其地址,通常只能出现在赋值语句的右侧。
1.2 判断左值和右值的简单方法
- 能否取地址:如果可以对表达式取地址(
&expr
),则为左值;否则为右值。 - 是否具名:具名对象通常为左值,匿名的临时对象为右值。
1.3 示例
|
|
二、C++11 中的值类别扩展
C++11 对值类别进行了重新分类,引入了新的概念,使得表达式的分类更加精确。这些值类别包括:
- 左值(lvalue)
- 亡值(xvalue,eXpiring value)
- 纯右值(prvalue,Pure rvalue)
- 泛左值(glvalue,Generalized lvalue)
- 右值(rvalue)
2.1 新的值类别定义
-
左值(lvalue):表示标识实体的表达式,可以获取对象的身份(地址)。通常是具名变量或可持久化的存储。
-
亡值(xvalue):表示即将被移动的对象,资源可以被重用,但仍然具有对象的身份。例如,函数返回的右值引用。
-
纯右值(prvalue):表示不具有身份的纯值,用于初始化对象或计算表达式结果。例如,字面量、临时对象、算术表达式的结果。
-
泛左值(glvalue):左值和亡值的统称,表示具有对象身份的表达式。
-
右值(rvalue):纯右值和亡值的统称,表示没有特定存储的值或即将被移动的对象。
2.2 值类别之间的关系
值类别之间的关系可以用以下关系图表示:
|
|
- glvalue(泛左值):包括 lvalue 和 xvalue
- rvalue(右值):包括 xvalue 和 prvalue
- xvalue(亡值):同时属于 glvalue 和 rvalue
2.3 值类别的含义
-
lvalue:具有持久存储,可以获取地址。典型的左值包括变量、函数、数组元素、解引用指针等。
-
xvalue:表示资源可被重用的对象,即将被移动。典型的亡值包括函数返回的右值引用、
std::move
的结果等。 -
prvalue:不具有对象身份,仅表示一个值。典型的纯右值包括字面量、临时对象、算术表达式的结果等。
2.4 示例
|
|
MyClass()
:纯右值(prvalue)std::move(rref)
:亡值(xvalue)
三、左值引用与右值引用
3.1 左值引用(Lvalue Reference)
- 语法:
Type& ref_name;
- 绑定对象:只能绑定到左值。
- 用途:为左值创建别名,常用于参数传递和返回类型。
3.2 右值引用(Rvalue Reference)
- 语法:
Type&& ref_name;
- 绑定对象:只能绑定到右值(包括纯右值和亡值)。
- 用途:捕获右值,以实现移动语义和完美转发。
3.3 引用的绑定规则
- 左值引用:只能绑定到左值。
- 右值引用:只能绑定到右值。
- 常量左值引用(
const Type&
):可以绑定到左值和右值,包括临时对象和字面量。
3.4 示例
|
|
四、右值引用的用途:移动语义与完美转发
4.1 移动语义(Move Semantics)
- 目的:通过移动资源(如内存、文件句柄等),避免不必要的拷贝,提高程序性能。
- 实现:提供移动构造函数和移动赋值运算符,将资源从源对象转移到目标对象。
4.2 移动构造函数与移动赋值运算符
-
移动构造函数:
1
MyClass(MyClass&& other) noexcept;
-
移动赋值运算符:
1
MyClass& operator=(MyClass&& other) noexcept;
-
实现要点:在移动过程中,转移资源所有权,并将源对象置于可析构的安全状态。
4.3 完美转发(Perfect Forwarding)
-
目的:在模板函数中,无论传入的是左值还是右值,都能保持其值类别,正确地转发给其他函数。
-
实现:使用模板参数的右值引用和
std::forward
。 -
示例:
1 2 3 4
template <typename T, typename Arg> std::shared_ptr<T> factory(Arg&& arg) { return std::make_shared<T>(std::forward<Arg>(arg)); }
五、常量左值引用的特殊性
5.1 常量左值引用可以绑定右值
- 特性:
const Type&
可以绑定到左值和右值,包括临时对象和字面量。 - 作用:延长临时对象的生命周期,直到引用超出作用域。
5.2 示例
|
|
- 在上述示例中,
printValue
函数的参数是const int&
,可以绑定到左值x
,也可以绑定到右值20
。
5.3 注意事项
- 不可修改:由于引用的是常量,无法在函数内部修改其值。
- 延长生命周期:绑定到右值时,临时对象的生命周期被延长到引用的作用域结束。
六、示例代码分析
6.1 捕获临时对象的右值引用
|
|
输出:
|
|
分析:
createObject()
返回一个临时对象(纯右值)。- 使用右值引用
MyClass&& rref
绑定临时对象,延长其生命周期到rref
作用域结束。 - 在
main
函数结束时,临时对象被析构。
6.2 移动构造函数的使用
|
|
输出:
|
|
分析:
createVector()
中的vec
是一个局部对象,返回时会触发移动构造函数,而不是拷贝构造函数(已被删除)。- 通过移动语义,
myVec
获得了vec
的数据,避免了不必要的深拷贝,提高了效率。
七、总结
-
C++11 对值类别进行了重新定义和扩展,引入了 左值(lvalue)、亡值(xvalue)、纯右值(prvalue)、泛左值(glvalue) 和 右值(rvalue)。
-
理解值类别之间的关系 对于正确使用右值引用、移动语义和完美转发非常重要。
-
右值引用 使得我们可以捕获右值,实现资源的移动,避免不必要的拷贝。
-
常量左值引用 的特殊性在于可以绑定到右值,延长临时对象的生命周期,但无法修改其值。
-
移动语义和完美转发 是 C++11 中提高性能和泛型编程能力的重要特性。