对错误条件发信号,并执行错误处理代码。
语法
throw 表达式 | (1) | |
throw | (2) | |
解释
- 更多关于 try 与 catch(异常处理)块的信息见 try-catch 块
1) 首先,从 表达式 复制初始化异常对象
- 这可能调用右值表达式的移动构造函数
- 这亦可能通过如 return 语句中一般的二步重载决议调用左值表达式的移动构造函数,若它们指名局部变量或是函数或 catch 子句的形参,且其作用域不超出最内层外围 try 块(若存在) | (C++17 起) |
- 复制/移动可能为复制消除所处理
- 即使复制初始化选择移动构造函数,从左值复制初始化仍必须为良式,且析构函数必须可访问 | (C++14 起) |
- 然后转移控制给最近进入其复合语句或成员初始化器列表,且未由此执行线程退出的,拥有匹配类型的异常处理块。
2) 重抛当前处理的异常。中止当前 catch 块的执行并将控制转移到下个匹配的异常处理块(但不是到同一 try 块的下个 catch 子句:其复合语句被认为已经‘退出’),重用既存的异常对象:不生成新对象。此形式只在现在正在处理异常时允许(其他情况中使用时将调用 std::terminate)。对于构造函数,关联到函数 try 块 的 catch 子句必须通过重抛出退出。
关于在异常处理期间引发错误,见 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 | 从局部变量的析构函数抛出时会跳过返回值的析构函数 | 添加函数返回值到回溯过程 |