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 | 从局部变量的析构函数抛出时会跳过返回值的析构函数 | 添加函数返回值到回溯过程 |