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++ 关键词
throw 表达式
对错误条件发信号,并执行错误处理代码。
语法
throw 表达式
|
(1) | ||||||||
throw
|
(2) | ||||||||
解释
- 更多关于 try 与 catch(异常处理)块的信息见 try-catch 块
- 这可能调用右值表达式的移动构造函数
|
(C++17 起) |
- 复制/移动可能为复制消除所处理
|
(C++14 起) |
- 然后转移控制给最近进入其复合语句或成员初始化器列表,且未由此执行线程退出的,拥有匹配类型的异常处理块。
关于在异常处理期间引发错误,见 std::terminate 与 std::unexpected。
异常对象
异常对象是由 throw
表达式在未指明的存储中构造的临时对象。
异常对象的类型是除去顶层 cv 限定符的 表达式 的静态类型。数组与函数类型分别调整到指针和函数指针类型。若异常对象的类型是不完整类型或除了指向(可有 cv 限定的)void 的指针以外的不完整类型的指针,则该 throw 表达式导致编译时错误。若 表达式 的类型为类类型,则其复制/移动构造函数和析构函数必须可访问,纵使发生复制消除也是如此。
不同于其他临时对象,异常对象在初始化 catch 子句形参时被认为是左值,故它可用左值引用捕捉、修改及重抛。
异常对象持续到最后一条不以重抛而退出的 catch 子句(若不以重抛而退出,则它紧跟 catch 子句的形参销毁之后被销毁),或持续到引用此对象的最后一个 std::exception_ptr 被销毁(该情况下异常对象正好在 std::exception_ptr 的析构函数返回前被销毁)。
栈回溯
一旦构造好异常对象,控制流即反向(沿调用栈向上)直至它抵达一个 try 块的起点,在该点按出现顺序将其每个关联的 catch
块的形参和异常对象的类型进行比较,以找到一个匹配(此过程的细节见 try-catch)。若找不到匹配,则控制流继续回溯栈直至下个 try
块,此后亦然。若找到匹配,则控制流跳到匹配的 catch
块。
因为控制流沿调用栈向上移动,所以它会为自进入相应 try 块之后的所有具有自动存储期的已构造但尚未销毁的对象,以其构造函数完成的逆序调用析构函数。当从 return 语句所使用的局部变量或临时量的构造函数中抛出异常时,从函数返回的对象的析构函数亦会得到调用。 (C++14 起)
若异常从某个对象的构造函数或(罕见地)从析构函数抛出(不管该对象的存储期),则对所有已完整构造的非静态非变体 (C++14 前)成员和基类,以其构造函数完成的逆序调用析构函数。联合体式的类的变体成员仅在从构造函数中回溯的情况中销毁,且若初始化与销毁之间改变了活动成员,则行为未定义。 (C++14 起)
若在非委托构造函数成功完成前,委托构造函数以异常退出,则调用此对象的析构函数。 |
(C++11 起) |
若从 new 表达式所调用的构造函数抛出异常,则调用匹配的解分配函数,若它可用。
此过程被称为栈回溯(stack unwinding)。
若由栈回溯机制所直接调用的函数,在异常对象初始化后、异常处理块开始执行前,以异常退出,则调用 std::terminate。这种函数包括退出作用域的具有自动存储期的对象的析构函数,和为初始化以值捕获的实参而调用(若未被消除)的复制构造函数。
若异常被抛出但未被捕获,包括从 std::thread 的启动函数,main 函数,及任何静态或线程局部对象的构造函数或析构函数中脱离的异常,则调用 std::terminate。对未捕获异常是否进行任何栈回溯是由实现定义的。
注解
在重抛异常时,必须使用第二个形式,以避免异常对象使用继承的(典型)情况中发生对象切片:
try { std::string("abc").substr(10); // 抛出 std::length_error } catch(const std::exception& e) { std::cout << e.what() << '\n'; // throw e; // 复制初始化一个 std::exception 类型的新异常对象 throw; // 重抛 std::length_error 类型的异常对象 }
throw 表达式被归类为 void 类型的纯右值表达式。与任何其他表达式一样,它可以是另一表达式中的子表达式,最常见于条件运算符:
double f(double d) { return d > 1e7 ? throw std::overflow_error("too big") : d; } int main() { try { std::cout << f(1e10) << '\n'; } catch (const std::overflow_error& e) { std::cout << e.what() << '\n'; } }
关键词
示例
#include <iostream> #include <stdexcept> struct A { int n; A(int n = 0): n(n) { std::cout << "A(" << n << ") constructed successfully\n"; } ~A() { std::cout << "A(" << n << ") destroyed\n"; } }; int foo() { throw std::runtime_error("error"); } struct B { A a1, a2, a3; B() try : a1(1), a2(foo()), a3(3) { std::cout << "B constructed successfully\n"; } catch(...) { std::cout << "B::B() exiting with exception\n"; } ~B() { std::cout << "B destroyed\n"; } }; struct C : A, B { C() try { std::cout << "C::C() completed successfully\n"; } catch(...) { std::cout << "C::C() exiting with exception\n"; } ~C() { std::cout << "C destroyed\n"; } }; int main () try { // 创建 A 基类子对象 // 创建 B 的成员 a1 // 创建 B 的成员 a2 失败 // 回溯销毁 B 的 a1 成员 // 回溯销毁 A 基类子对象 C c; } catch (const std::exception& e) { std::cout << "main() failed to create C with: " << e.what(); }
输出:
A(0) constructed successfully A(1) constructed successfully A(1) destroyed B::B() exiting with exception A(0) destroyed C::C() exiting with exception main() failed to create C with: error
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1866 | C++14 | 从构造函数栈回溯时会泄露变体成员 | 变体成员被销毁 |
CWG 1863 | C++14 | 在抛出时对仅移动异常对象不要求复制构造函数,但允许之后复制 | 要求复制构造函数 |
CWG 2176 | C++14 | 从局部变量的析构函数抛出时会跳过返回值的析构函数 | 添加函数返回值到回溯过程 |