C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
值类别
每个 C++ 表达式(带有操作数的操作符、字面量、变量名等)可按照两种独立的特性加以辨别:类型和值类别 (value category)。每个表达式都具有某种非引用类型,且每个表达式只属于三种基本值类别中的一种:纯右值 (prvalue)、亡值 (xvalue)、左值 (lvalue)。
|
(C++17 起) |
注意:这个分类法与 C++ 标准过去的各版本相比,经历了显著变更,细节见下文的历史部分。
基本类别
左值
下列表达式是左值表达式:
- 变量、函数、模板形参对象 (C++20 起)或数据成员之名,不论其类型,例如 std::cin 或 std::endl。即使变量的类型是右值引用,由其名字构成的表达式仍是左值表达式;
- 返回类型为左值引用的函数调用或重载运算符表达式,例如 std::getline(std::cin, str)、std::cout << 1、str1 = str2 或 ++it;
- a = b,a += b,a %= b,以及所有其他内建的赋值及复合赋值表达式;
- ++a 和 --a,内建的前置自增与前置自减表达式;
- *p,内建的间接寻址表达式;
- a[n] 和 n[a],内建的下标表达式,但
a[n]
中的一个操作数应为数组左值 (C++11 起); - a.m,对象成员表达式,除了
m
为成员枚举项或非静态成员函数,或者a
为右值而m
为非引用类型的非静态数据成员的情况; - p->m,内建的指针成员表达式,除了
m
为成员枚举项或非静态成员函数的情况; - a.*mp,对象的成员指针表达式,其中
a
是左值且mp
是数据成员指针; - p->*mp,内建的指针的成员指针表达式,其中
mp
是数据成员指针; - a, b,内建的逗号表达式,其中
b
是左值; - a ? b : c,对某些
b
和c
的三元条件表达式(例如,当它们都是同类型左值时,但细节见其定义); - 字符串字面量,例如 "Hello, world!";
- 转换为左值引用类型的转型表达式,例如 static_cast<int&>(x);
|
(C++11 起) |
性质:
- 与泛左值相同(见下文)。
- 可以取左值的地址:&++i[1] 及 &std::endl 是合法表达式。
- 可修改的左值可用作内建赋值和内建复合赋值运算符的左操作数。
- 左值可用于初始化左值引用;这会将一个新名字关联给该表达式所标识的对象。
纯右值
下列表达式是纯右值表达式:
- (除了字符串字面量之外的)字面量,例如 42、true 或 nullptr;
- 返回类型是非引用的函数调用或重载运算符表达式,例如 str.substr(1, 2)、str1 + str2 或 it++;
- a++ 和 a--,内建的后置自增与后置自减表达式;
- a + b、a % b、a & b、a << b,以及其他所有内建的算术表达式;
- a && b、a || b、!a,内建的逻辑表达式;
- a < b、a == b、a >= 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,对某些
b
和c
的三元条件表达式(细节见其定义); - 转换为非引用类型的转型表达式,例如 static_cast<double>(x)、std::string{} 或 (int)42;
- this 指针;
- 枚举项;
- 非类型模板形参,除非其类型为类或 (C++20 起)左值引用类型;
|
(C++11 起) |
|
(C++20 起) |
性质:
- 与右值相同(见下文)。
- 纯右值不能多态:它所标识的对象的动态类型始终为该表达式的类型。
- 非类非数组的纯右值不能有 cv 限定。(注意:函数调用或转型表达式可能生成非类的 cv 限定类型的纯右值,但其 cv 限定符被立即剥除。)
- 纯右值不能具有不完整类型(除了类型
void
(见下文),或在 decltype 说明符中使用之外) - 纯右值不能具有抽象类类型或其数组类型。
亡值
下列表达式是亡值表达式:
- 返回类型为对象的右值引用的函数调用或重载运算符表达式,例如 std::move(x);
- a[n],内建的下标表达式,其操作数之一是数组右值;
- a.m,对象成员表达式,其中
a
是右值且m
是非引用类型的非静态数据成员; - a.*mp,对象的成员指针表达式,其中
a
为右值且mp
为数据成员指针; - a ? b : c,对某些
b
和c
的三元条件表达式(细节见其定义); - 转换为对象的右值引用类型的转型表达式,例如 static_cast<char&&>(x);
|
(C++17 起) |
性质:
- 与右值相同(见下文)。
- 与泛左值相同(见下文)。
特别是,与所有的右值类似,亡值可以绑定到右值引用上,而且与所有的泛左值类似,亡值可以是多态的,而且非类的亡值可以有 cv 限定。
混合类别
泛左值
泛左值表达式包括左值、亡值。
性质:
右值
右值表达式包括纯右值、亡值。
性质:
- 右值不能被取地址:&int()、&i++[3]、&42 及 &std::move(x) 是非法的。
- 右值不能用作内建赋值运算符及内建复合赋值运算符的左操作数。
- 右值可以用于初始化 const 左值引用,这种情况下该右值所标识的对象的生存期被延长到该引用的作用域结尾。
(C++11 起) |
特殊类别
未决成员函数调用
表达式 a.mf 与 p->mf,其中 mf
是非静态成员函数,以及表达式 a.*pmf 与 p->*pmf,其中 pmf
是成员函数指针,被归类为纯右值表达式,但它们不能用来初始化引用,作为函数实参,或者用于除了作为函数调用运算符的左操作数(例如 (p->*pmf)(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) 表达式”。左值和亡值都是泛左值表达式。
可被移动的表达式被称作“右值 (rvalue) 表达式”。纯右值和亡值都是右值表达式。
C++17
C++17 中,某些场合强制要求进行复制消除,而这要求将纯右值表达式从被它们所初始化的临时对象中分离出来,这就是我们现有的系统。要注意,与 C++11 的方案相比,纯右值已不再是可被移动。
脚注
- ↑ 假设 i 具有内建类型,或者其前置自增运算符被重载为返回左值引用。
- ↑ 2.0 2.1 2.2 2.3 一种特殊的右值类别,参见未决成员函数调用。
- ↑ 假定 i 具有内建类型,或其后置自增运算符并未重载为返回左值引用。
- ↑ “C 社区中对此的观点主要围绕着 lvalue 的含义而有所差异,一组人认为 lvalue 是任何种类的对象定位器,另一组人认为 lvalue 在赋值运算符的左侧时才有意义。C89 委员会采纳了作为对象定位器的定义。”—— ANSI C 基本原理,6.3.2.1/10 。
- ↑ 新·值术语,Bjarne Stroustrup,2010。
- ↑ const 纯右值(仅允许类类型)及 const 亡值不能绑定于 T&& 的重载,但它们可以绑定于 const T&& 的重载——由于其满足这种分类中的“可被移动”的定义,标准中将这种重载也归类为“移动构造函数”和“移动赋值运算符”。然而,这种重载无法修改它们的参数,并且在实践中并不使用;当没有这种重载时,const 纯右值和 const 亡值绑定于 const T& 的重载。
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 616 | C++11 | 右值的成员访问及通过成员指针的成员访问的结果为纯右值 | 重分类为亡值 |
CWG 1213 | C++11 | 数组右值的下标操作导致左值 | 重分类为亡值 |