对错误条件发信号,并执行错误处理代码。

语法

throw 表达式 (1)
throw (2)

解释

  • 更多关于 trycatch(异常处理)块的信息见 try-catch 块

1) 首先,从 表达式 复制初始化异常对象

  • 这可能调用右值表达式的移动构造函数


- 这亦可能通过如 return 语句中一般的二步重载决议调用左值表达式的移动构造函数,若它们指名局部变量或是函数或 catch 子句的形参,且其作用域不超出最内层外围 try 块(若存在)
(C++17 起)


- 即使复制初始化选择移动构造函数,从左值复制初始化仍必须为良式,且析构函数必须可访问
(C++14 起)
  • 然后转移控制给最近进入其复合语句或成员初始化器列表,且未由此执行线程退出的,拥有匹配类型的异常处理块

2) 重抛当前处理的异常。中止当前 catch 块的执行并将控制转移到下个匹配的异常处理块(但不是到同一 try 块的下个 catch 子句:其复合语句被认为已经‘退出’),重用既存的异常对象:不生成新对象。此形式只在现在正在处理异常时允许(其他情况中使用时将调用 std::terminate)。对于构造函数,关联到函数 try 块 的 catch 子句必须通过重抛出退出。

关于在异常处理期间引发错误,见 std::terminatestd::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。对未捕获异常是否进行任何栈回溯是由实现定义的。

注解

在重抛异常时,必须使用第二个形式,以避免异常对象使用继承的(典型)情况中发生对象切片:

  1. try {
  2. std::string("abc").substr(10); // 抛出 std::length_error
  3. } catch(const std::exception& e) {
  4. std::cout << e.what() << '\n';
  5. // throw e; // 复制初始化一个 std::exception 类型的新异常对象
  6. throw; // 重抛 std::length_error 类型的异常对象
  7. }

throw 表达式被归类为 void 类型的纯右值表达式。与任何其他表达式一样,它可以是另一表达式中的子表达式,最常见于条件运算符

  1. double f(double d)
  2. {
  3. return d > 1e7 ? throw std::overflow_error("too big") : d;
  4. }
  5. int main()
  6. {
  7. try {
  8. std::cout << f(1e10) << '\n';
  9. } catch (const std::overflow_error& e) {
  10. std::cout << e.what() << '\n';
  11. }
  12. }

关键词

throw

示例

运行此代码

  1. #include <iostream>
  2. #include <stdexcept>
  3.  
  4. struct A {
  5. int n;
  6. A(int n = 0): n(n) { std::cout << "A(" << n << ") constructed successfully\n"; }
  7. ~A() { std::cout << "A(" << n << ") destroyed\n"; }
  8. };
  9.  
  10. int foo()
  11. {
  12. throw std::runtime_error("error");
  13. }
  14.  
  15. struct B {
  16. A a1, a2, a3;
  17. B() try : a1(1), a2(foo()), a3(3) {
  18. std::cout << "B constructed successfully\n";
  19. } catch(...) {
  20. std::cout << "B::B() exiting with exception\n";
  21. }
  22. ~B() { std::cout << "B destroyed\n"; }
  23. };
  24.  
  25. struct C : A, B {
  26. C() try {
  27. std::cout << "C::C() completed successfully\n";
  28. } catch(...) {
  29. std::cout << "C::C() exiting with exception\n";
  30. }
  31. ~C() { std::cout << "C destroyed\n"; }
  32. };
  33.  
  34. int main () try
  35. {
  36. // 创建 A 基类子对象
  37. // 创建 B 的成员 a1
  38. // 创建 B 的成员 a2 失败
  39. // 回溯销毁 B 的 a1 成员
  40. // 回溯销毁 A 基类子对象
  41. C c;
  42. } catch (const std::exception& e) {
  43. std::cout << "main() failed to create C with: " << e.what();
  44. }

输出:

  1. A(0) constructed successfully
  2. A(1) constructed successfully
  3. A(1) destroyed
  4. B::B() exiting with exception
  5. A(0) destroyed
  6. C::C() exiting with exception
  7. main() failed to create C with: error

缺陷报告

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

DR 应用于 出版时的行为 正确行为
CWG 1866 C++14 从构造函数栈回溯时会泄露变体成员 变体成员被销毁
CWG 1863 C++14 在抛出时对仅移动异常对象不要求复制构造函数,但允许之后复制 要求复制构造函数
CWG 2176 C++14 从局部变量的析构函数抛出时会跳过返回值的析构函数 添加函数返回值到回溯过程