1. 一个直观的、历史的定义 (C++11之前)
在最初的 C 语言以及 C++98/03 标准中,定义相对简单:
- 
	左值 (lvalue): 可以出现在赋值语句左侧 的表达式。它代表一个有持久状态、有名称、可以取地址的内存位置。 - 
		例如:变量、函数返回的左值引用、解引用指针 ( *ptr)、数组元素arr[0]。
- 
		int a = 10;//a是左值
- 
		&a;// 合法,可以取地址
 
- 
		
- 
	右值 (rvalue): 只能出现在赋值语句右侧 的表达式。它代表一个临时的、短暂的、没有名称、无法取地址的值。 - 
		例如:字面量 ( 42,"hello")、临时对象、函数返回的非引用类型。
- 
		int b = 20;//20是右值
- 
		&20;// 非法!不能取字面量的地址
- 
		int foo() { return 5; };int c = foo();//foo()的返回值是右值
 
- 
		
这个定义直观易懂,但在 C++11 引入移动语义后,就显得不够精确了。
2. 更精确的现代 C++ 定义:值类别 (Value Categories)
C++11 标准引入了更复杂的值类别 (Value categories) 模型,对表达式进行了更精细的分类。理解下面的图表至关重要:
表达式 (expression) 首先分为两个大类:
- 
	泛左值 (glvalue): 确定了一个对象、位域或函数的身份(identity)的表达式(即它有标识符,可以取地址)。 
- 
	右值 (rvalue): 适合用于初始化对象或为赋值运算符的操作数的表达式(即通常代表一个可被移动的临时值)。 
然后,这两个大类可以交叉组合,形成我们最常讨论的三种主要类别:
- 
	左值 (lvalue): 它是 泛左值,但不是右值。 - 
		特点:有标识符,可取地址,不可被移动。 
- 
		例子: - 
			变量名、函数名: x,std::cin
- 
			返回左值引用的函数调用: std::cout << 1
- 
			赋值表达式: a = b
- 
			前缀自增/减: ++a
 
- 
			
 
- 
		
- 
	将亡值 (xvalue): 它是 泛左值,同时也是右值。可以理解为“即将消亡的值”(eXpiring value)。 - 
		特点:有标识符(曾经有名字),但它的资源可以被“掠夺”(移动)。它是连接左值和右值的桥梁。 
- 
		例子: - 
			返回右值引用的函数调用,最典型的就是 std::move():std::move(x)
- 
			转换为右值引用的转换表达式: static_cast<T&&>(t)
- 
			访问一个将亡值的成员: myClass.getTemporary().data(假设.getTemporary()返回一个临时对象)
 
- 
			
 
- 
		
- 
	纯右值 (prvalue): 它是 右值,但不是泛左值。 - 
		特点:没有标识符,不可取地址,通常是字面量或用于初始化对象的临时值。它的资源可以被移动,但通常它本身就直接用于构造目标对象。 
- 
		例子: - 
			字面量: 42,true,nullptr
- 
			返回非引用类型的函数调用: str1 + str2(返回临时字符串),foo()
- 
			lambda 表达式: []{ return 0; }
- 
			this指针
 
- 
			
 
- 
		
简单总结现代分类:
| 类别 | 中文名 | 是否有标识符(可取地址) | 是否可被移动 | 例子 | 
|---|---|---|---|---|
| lvalue | 左值 | 是 | 否 | 变量 a | 
| xvalue | 将亡值 | 是 (但即将消亡) | 是 | std::move(a) | 
| prvalue | 纯右值 | 否 | 是 (隐式) | 100,func() | 
而 右值 (rvalue) 是 将亡值 (xvalue) 和 纯右值 (prvalue) 的统称。
3. 为什么需要区分?移动语义的动机
区分左值和右值的核心目的是为了实现移动语义 (Move Semantics),避免不必要的深度拷贝,提升性能。
- 
	拷贝语义 (Copy Semantics): 对于左值,我们通常进行“拷贝”。如果对象管理着堆内存(如 std::vector,std::string),拷贝意味着分配新内存并复制所有数据,成本高昂。cpp std::vector<int> v1 = {1, 2, 3, 4, 5}; // v1 是左值 std::vector<int> v2 = v1; // 拷贝构造发生!需要复制所有数据。
- 
	移动语义 (Move Semantics): 对于右值(特别是将亡值),我们知道它马上就要被销毁了,与其拷贝它的资源,不如直接“偷”过来(移动)。这通常只是复制几个指针并把源对象的指针置空,成本极低。 cpp std::vector<int> v3 = std::move(v1); // 移动构造发生!v1 的资源被“移动”到 v3。 // 此后,v1 处于有效但未定义的状态(通常为空)。 
编译器会为类生成移动构造函数和移动赋值运算符,它们接受 右值引用 (T&&) 作为参数。当用右值初始化对象时,编译器会优先选择更高效的移动操作而不是拷贝操作。
- 
	T&(左值引用): 只能绑定到左值。
- 
	const T&(常量左值引用): 可以绑定到左值和右值,但它不允许修改,因此只能用于拷贝。
- 
	T&&(右值引用): 只能绑定到右值(将亡值和纯右值)。这是实现移动语义的关键。
4. 实践中的应用与判断
如何判断一个表达式是左值还是右值?
- 
	能否取地址? 能用 &取地址的是左值或将亡值(泛左值),绝对不能取地址的是纯右值。
- 
	它是否有名称? 有名称的变量通常是左值。即使这个变量被声明为右值引用,它本身也是一个左值! cpp void foo(int&& param) { // param 是一个右值引用,但它本身有名字,所以 param 是左值! // 在函数内部,可以对 param 取地址 (¶m) }  int a = 10; foo(std::move(a)); // std::move(a) 产生一个将亡值(右值),用来初始化右值引用 param这就是为什么在移动构造函数内部,我们仍然需要用 std::move()来将成员变量再次转换为右值。
std::move 和 std::forward 的作用
- 
	std::move(): 它的本质是一个无条件的转换工具。它不做任何移动操作,只是将一个左值(或左值引用)强制转换为一个右值引用(将亡值),从而允许后续使用移动语义。cpp template <typename T> decltype(auto) move(T&& param) { // 忽略实现细节,本质是: return static_cast<typename std::remove_reference<T>::type&&>(param); }
- 
	std::forward(): 它是一个有条件的转换工具,用于完美转发 (Perfect Forwarding)。它在模板函数中保持参数的原始值类别(左值保持左值,右值保持右值)。cpp template <typename T> void wrapper(T&& arg) { // 这里是万能引用,既可以是左值引用也可以是右值引用 // 根据 T 的类型推导,将 arg 以原来的值类别传递给另一个函数 some_function(std::forward<T>(arg)); }  Widget w; wrapper(w); // T 是 Widget&, forward 后传给 some_function 的是左值 wrapper(std::move(w)); // T 是 Widget, forward 后传给 some_function 的是右值
总结
- 
	核心区分:左值有持久身份;右值(特别是纯右值)是临时值。 
- 
	关键目的:支持移动语义,通过区分值类别来选择拷贝(左值)还是移动(右值),从而提升性能。 
- 
	重要工具: - 
		右值引用 ( T&&): 用于绑定右值,实现移动操作。
- 
		std::move: 无条件产生右值引用,启用移动语义。
- 
		std::forward: 在模板中完美保持参数的值类别。
 
- 
		
- 
	易错点:命名的右值引用本身是左值。 
 
                   
                   
                   
                   
       
           
                 
                 
                 
                 
                 
                
               
                 
                 
                 
                 
                
               
                 
                 扫一扫
扫一扫
                     
              
             
                   7779
					7779
					
 被折叠的  条评论
		 为什么被折叠?
被折叠的  条评论
		 为什么被折叠?
		 
		  到【灌水乐园】发言
到【灌水乐园】发言                                
		 
		 
    
   
    
   
             
            


 
            