值类别

来自cppreference.com
< cpp‎ | language
 
 
 
表达式
概述
值类别(左值 lvalue、右值 rvalue、亡值 xvalue)
求值顺序(序列点)
常量表达式
不求值表达式
初等表达式
lambda 表达式(C++11)
字面量
整数字面量
浮点字面量
布尔字面量
字符字面量,包含转义序列
字符串字面量
空指针字面量(C++11)
用户定义字面量(C++11)
运算符
赋值运算符a=ba+=ba-=ba*=ba/=ba%=ba&=ba|=ba^=ba<<=ba>>=b
自增与自减++a--aa++a--
算术运算符+a-aa+ba-ba*ba/ba%b~aa&ba|ba^ba<<ba>>b
逻辑运算符a||ba&&b!a
比较运算符a==ba!=ba<ba>ba<=ba>=b
成员访问运算符a[b]*a&aa->ba.ba->*ba.*b
其他运算符a(...)a,ba?b:c
运算符的替代表示形式
优先级和结合性
折叠表达式(C++17)
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
运算符重载
类型转换
隐式转换
const_cast
static_cast
reinterpret_cast
dynamic_cast
显式转换 (T)aT(a)
用户定义转换
 

每个 C++ 表达式(运算符带上其操作数、字面量、变量名等)为两种各自独立的性质所辨别:类型值类别。每个表达式拥有某种非引用类型,且每个表达式恰属于三种基本值类别之一:纯右值(prvalue)亡值(xvalue)左值(lvalue),定义如下:

  • 泛左值(glvalue)是其求值确定一个对象、位域或函数的个体的表达式;
  • 纯右值(prvalue)是这样的表达式,其求值
  • 计算某个运算符的操作数的值(这种纯右值没有结果对象),或者
  • 初始化某个对象或位域(这种纯右值被称作具有结果对象)。所有类和数组的纯右值都拥有结果对象,即使它被舍弃。在某些语境中,出现临时量实质化,以创建作为结果对象的临时量;
  • 亡值(xvalue)是代表其资源能够被重新使用的对象或位域的泛左值;
  • 左值(lvalue)是非亡值的泛左值;
  • 右值(rvalue)是纯右值或者亡值。

注意:这个分类法与 C++ 标准过去的各版本相比,经历了显著变更,细节见下文的历史部分。

目录

[编辑] 基本类别

[编辑] 左值

下列表达式是左值表达式

  • 变量、函数或数据成员之名,不论其类型,例如 std::cinstd::endl。即使变量的类型是右值引用,由其名字构成的表达式仍是左值表达式;
  • 函数调用或重载运算符表达式,其返回类型是左值引用,例如 std::getline(std::cin, str)std::cout << 1str1 = str2++it
  • a = ba += ba %= b ,以及所有其他内建的赋值及复合赋值表达式;
  • ++a--a,内建的前置自增与前置自减表达式;
  • *p ,内建的间接表达式;
  • a[n]p[n] ,内建的下标表达式,除了 a 为数组右值的情况 (C++11 起)
  • a.m对象成员表达式,除了 m 为成员枚举符或非静态成员函数,或者 a 为右值且 m 为非引用类型的非静态数据成员的情况;
  • p->m ,内建的指针成员表达式,除了 m 为成员枚举符或非静态成员函数的情况;
  • a.*mp对象的成员指针表达式,其中 a 是左值且 mp 是指向数据成员的指针;
  • p->*mp ,内建的指针的成员指针表达式,其中 mp 是指向数据成员的指针;
  • a, b ,内建的逗号表达式,其中 b 是左值;
  • a ? b : c ,某些 bc三元条件表达式(例如,它们都是同类型左值时,但细节见定义);
  • 字符串字面量,例如 "Hello, world!"
  • 转换为左值引用类型的转型表达式,例如 static_cast<int&>(x)
  • 函数调用表达式或重载的运算符表达式,其返回类型是到函数的右值引用;
  • 转换为函数的右值引用类型的转型表达式,如 static_cast<void (&&)(int)>(x)
(C++11 起)

性质:

  • 与(下文的)泛左值相同。
  • 可以取左值的地址: &++i[1]&std::endl 是合法表达式。
  • 可修改的左值可用作内建赋值和内建复合赋值运算符的左操作数。
  • 左值可用于初始化左值引用;这会将一个新名字关联给该表达式所标识的对象。

[编辑] 纯右值

下列表达式是纯右值表达式

  • (除了字符串字面量之外的)字面量,例如 42truenullptr
  • 函数调用或重载运算符表达式,其返回类型是非引用,例如 str.substr(1, 2)str1 + str2it++
  • a++a--,内建的后置自增与后置自减表达式;
  • a + ba % ba & ba << b ,以及其他所有内建的算术表达式;
  • a && ba || b!a ,内建的逻辑表达式;
  • a < ba == ba >= b 以及其他所有内建的比较表达式;
  • &a,内建的取地址表达式;
  • a.m对象成员表达式,其中 m 是成员枚举符或非静态成员函数[2],或其中 a 为右值且 m 为非引用类型的非静态数据成员 (C++11 前)
  • p->m,内建的指针成员表达式,其中 m 为成员枚举符或非静态成员函数[2]
  • a.*mp对象的成员指针表达式,其中 mp 是指向成员函数的指针[2],或其中 a 为右值且 mp 为指向数据成员的指针 (C++11 前)
  • p->*mp,内建的指针的成员指针表达式,其中 mp 是指向成员函数的指针[2]
  • a, b,内建的逗号表达式,其中 b 是右值;
  • a ? b : c ,某些 bc三元条件表达式(细节见定义);
  • 转换为非引用类型的转型表达式,例如 static_cast<double>(x)std::string{}(int)42
  • this 指针;
  • 枚举项;
(C++11 起)
  • requires 表达式,例如 requires (T i) { typename T::type; }
  • 概念的特化,例如 EqualityComparable<int>
(C++20 起)

性质:

  • 与(下文的)右值相同。
  • 纯右值不能多态:它所标识的对象的动态类型始终为该表达式的类型。
  • 非类非数组的纯右值不能有 cv 限定。(注意:函数调用或转型表达式可能生成非类的 cv 限定类型纯右值,但 cv 限定符被立即剥除。)
  • 纯右值不能具有不完整类型。(除了类型 void(见下文),或在 decltype 指定符中使用之外)

[编辑] 亡值

下列表达式是亡值表达式

  • 函数调用或重载运算符表达式,其返回类型为右值引用,例如 std::move(x)
  • a[n],内建的下标表达式,其操作数之一是数组右值;
  • a.m对象成员表达式,其中 a 是右值且 m 是非引用类型的非静态数据成员;
  • a.*mp对象的成员指针表达式,其中 a 为右值且 mp 为指向数据成员的指针;
  • a ? b : c ,某些 bc三元条件表达式(细节见定义);
  • 转换为对象的右值引用类型的转型表达式,例如 static_cast<char&&>(x)
(C++17 起)

性质:

  • 与(下文的)右值相同。
  • 与(下文的)泛左值相同。

特别是,同所有的右值类似,亡值可以绑定到右值引用上,而且同所有的泛左值类似,亡值可以是多态的,而且非类的亡值可以有 cv 限定

[编辑] 混合类别

[编辑] 泛左值

泛左值表达式是左值或亡值。

性质:

  • 泛左值可以通过左值到右值、数组到指针或函数到指针隐式转换隐式转换成纯右值。
  • 泛左值可以是多态的:其所标识的对象的动态类型不必是该表达式的静态类型。
  • 泛左值可以具有不完整类型,只要表达式中容许。

[编辑] 右值

右值表达式是纯右值或亡值。

性质:

  • 不能取右值的地址: &int()&i++ [3]&42&std::move(x) 是非法的。
  • 右值不能用作内建赋值运算符及内建复合赋值运算符的左操作数。
  • 右值可以用于初始化 const 左值引用,这种情况下该右值所标识的对象的生存期被扩展到该引用的作用域结尾。
  • 右值可以用于初始化右值引用,这种情况下该右值所标识的对象的生存期被扩展到该引用的作用域结尾。
  • 当被用作函数实参且该函数有二种重载可用,其中之一接受右值引用的形参而另一个接受 const 的左值引用的形参时,右值将被绑定到右值引用的重载之上(从而,当复制与移动构造函数均可用时,以右值实参将调用其移动构造函数,复制和移动赋值运算符与此相似)。
(C++11 起)

[编辑] 特殊类别

[编辑] 未决成员函数调用

表达式 a.mfp->mf,其中 mf非静态成员函数,以及表达式 a.*mfpp->*mfp,其中 mfp指向成员函数的指针,被归类为纯右值表达式,但它们不能用来初始化引用,作为函数实参,或者用于除了作为函数调用运算符的左操作数(例如 (p->*mfp)(args))以外的任何目的。

[编辑] void 表达式

返回 void 的函数调用表达式,转换为 void 的转型表达式,以及 throw 表达式,被归类为纯右值表达式,但它们不能用来初始化引用或者作为函数实参。它们可以用在舍弃值的语境(例如自成一行,作为逗号运算符的左运算数等)和返回 void 的函数中的 return 语句中。另外,throw 表达式可用作条件运算符 ?: 的第二个和第三个操作数。

void 表达式没有结果对象

(C++17 起)

[编辑] 位域

代表某个位域的表达式(例如 a.m,其中 a 是类型 struct A { int m: 3; } 的左值)是左值表达式:它可用作赋值运算符的左操作数,但它不能被取地址,并且非 const 的左值引用不能绑定于它。const 左值引用可以以位域左值进行初始化,但这会制造位域的一个临时副本:它不会直接绑定到位域。

[编辑] 历史

[编辑] CPL

编程语言 CPL 率先为表达式引入了值类别:所有 CPL 表达式都能以“右侧模式( right-hand mode )”求值,但只有某些类型的表达式在“左侧模式( left-hand mode )”有意义。在右侧模式中求值时,表达式被当做一条进行值的计算(右侧值,或右值)的规则。在左侧模式中求值时,表达式的效果则为给出一个地址(左侧值,或左值)。“左”和“右”代表“赋值之左”和“赋值之右”。

[编辑] C

C 编程语言遵循相似的分类法,但赋值的作用不再重要:C 的表达式被分为“左值( lvalue )表达式”和其他(函数和非对象值),其中“左值( lvalue )”的含义为标识一个对象的表达式,即“定位器值( locator value )”[4]

[编辑] C++98

2011 年前的 C++ 遵循 C 模型,但恢复了对非左值表达式的“右值( rvalue )”称呼,令函数为左值,并添加了引用能绑定到左值但唯有 const 的引用能绑定到右值的规则。几种非左值的 C 表达式在 C++ 中成为了左值表达式。

[编辑] C++11

随着移动语义引入到 C++11 之中,值类别被重新进行了定义,以区别表达式的两种独立的性质[5]

  • 具有同一性( identity ):可以确定表达式是否与另一表达式指代同一实体,例如通过比较它们所标识的对象或函数的(直接或间接获得的)地址;
  • 可被移动移动构造函数移动赋值运算符或实现了移动语义的其他函数重载能够绑定于这个表达式。

C++11 中:

  • 具有同一性且不可被移动的表达式被称作左值( lvalue )表达式;
  • 具有同一性且可被移动的表达式被称作亡值( xvalue )表达式;
  • 不具有同一性且可被移动的表达式被称作纯右值( prvalue )表达式;
  • 不具有同一性且不可被移动的表达式无法被使用[6]

具有同一性的表达式被称作“泛左值表达式( glvalue expressions )”。左值和亡值都是泛左值表达式。

可被移动的表达式被称作“右值表达式( rvalue expressions )”。纯右值和亡值都是右值表达式。

[编辑] C++17

C++17 中,某些场合强制要求进行复制消除,而这要求将纯右值表达式从被它们所初始化的临时对象中分离出来,这就是我们现有的系统。注意,与 C++11 方案相反,纯右值不再是移动来源。

[编辑] 脚注

  1. 假设 i 具有内建类型,或者其前置自增运算符被重载为返回左值引用。
  2. 2.0 2.1 2.2 2.3 特殊的右值类别,参见未决成员函数调用。
  3. 假定 i 拥有内建类型,或其后置自增运算符并未重载为返回左值引用。
  4. “C 社区中对此的观点主要围绕着 lvalue 的含义而有所差异,一组人认为 lvalue 是任何种类的对象定位器,另一组人认为 lvalue 在赋值运算符的左侧时才有意义。C89 委员会采纳了作为对象定位器的定义。”—— ANSI C 基本原理,6.3.2.1/10 。
  5. 新·值术语, Bjarne Stroustrup , 2010 。
  6. const 纯右值(仅允许类类型)及 const 亡值不能绑定于 T&& 的重载,但它们可以绑定于 const T&& 的重载——由于其满足这种分类中的“可被移动”的定义,标准中将这种重载也归类为“移动构造函数”和“移动赋值运算符”。然而,这种重载无法修改它们的参数,并且在实践中并不使用;当没有这种重载时,const 纯右值和 const 亡值绑定于 const T& 的重载。

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 616 C++11 右值的成员访问及通过成员指针的成员访问的结果为纯右值 重分类为亡值
CWG 1213 C++11 数组右值的下标操作导致左值 重分类为亡值

[编辑] 参阅

值类别C 文档