处理异常
异常可以由处理程序处理。
目录 |
[编辑] 处理程序
catch ( attr (可选) type-specifier-seq declarator ) compound-statement |
(1) | ||||||||
catch ( attr (可选) type-specifier-seq abstract-declarator (可选) ) compound-statement |
(2) | ||||||||
catch ( ... ) compound-statement |
(3) | ||||||||
属性 | - | (C++11 起) 任意数量的属性,应用于参数 |
type-specifier-seq | - | 形式参数声明的一部分,与函数参数列表中相同 |
声明符 | - | 参数声明的一部分,与函数参数列表中相同 |
abstract-declarator | - | 未命名参数声明的一部分,与函数参数列表中相同 |
复合语句 | - | 复合语句 |
处理程序中的参数声明描述了可能导致该处理程序进入的异常类型。
如果参数被声明为以下类型之一,则程序格式错误
|
(C++11 起) |
- 指向非(可能带cv限定的)void 的不完整类型的指针
- 对不完整类型的左值引用
如果参数被声明为“T 数组”类型或函数类型 T,则该类型被调整为“指向 T 的指针”。
参数类型为 T 的处理程序可以简写为“T 类型处理程序”。
[编辑] 匹配异常
每个 try 块都关联着许多处理程序,这些处理程序形成一个处理程序序列。当从 try 块抛出异常时,序列中的处理程序按出现的顺序尝试匹配该异常。
如果满足以下任何条件,则处理程序与类型为 E
的异常对象匹配
- 处理程序的类型是“可能是 cv 限定的
T
”或“对可能是 cv 限定的T
的左值引用”,并且满足以下任何条件
-
E
和T
是相同的类型(忽略顶层 cv 限定符)。 -
T
是E
的明确的公共基类。
-
- 处理程序的类型是“可能是 cv 限定的
T
”或 const T&,其中T
是指针或指向成员的指针类型,并且满足以下任何条件
-
E
是指针或指向成员的指针类型,可以通过以下至少一种转换转换为T
- 不涉及转换为指向私有、保护或模糊类的指针的标准指针转换。
-
(C++17 起) |
|
(C++11 起) |
catch (...) 处理程序匹配任何类型的异常。如果存在,它只能是处理程序序列中的最后一个处理程序。此处理程序可用于确保不会有任何未捕获的异常从提供不抛出异常保证的函数中逃逸。
try { f(); } catch (const std::overflow_error& e) {} // this executes if f() throws std::overflow_error (same type rule) catch (const std::runtime_error& e) {} // this executes if f() throws std::underflow_error (base class rule) catch (const std::exception& e) {} // this executes if f() throws std::logic_error (base class rule) catch (...) {} // this executes if f() throws std::string or int or any other unrelated type
如果 try 块的处理程序中没有找到匹配项,则会继续在动态上包围的 try 块的同一线程中(C++11 起)搜索匹配的处理程序。
如果未找到匹配的处理程序,则调用 std::terminate;在调用 std::terminate 之前堆栈是否展开是实现定义的。
[编辑] 处理异常
当抛出异常时,控制权会转移到具有匹配类型的最近的处理程序;“最近”是指其复合语句或成员初始化列表(如果存在)紧跟在 try 关键字之后,且最近由控制线程进入但尚未退出的处理程序。
[编辑] 初始化处理程序参数
参数列表中声明的参数(如果存在),类型为“可能是 cv 限定的 T
”或“对可能是 cv 限定的 T
的左值引用”,按如下方式从类型为 E
的异常对象初始化
- 如果
T
是E
的基类,则参数通过复制初始化从类型为T
的左值初始化,该左值指定异常对象的相应基类子对象。 - 否则,参数通过复制初始化从类型为
E
的左值初始化,该左值指定异常对象。
参数的生命周期在处理程序退出时结束,即在处理程序中初始化的任何具有自动存储期的对象被销毁之后。
当参数声明为对象时,对该对象的任何更改都不会影响异常对象。
当参数声明为对象的引用时,对引用对象的任何更改都是对异常对象的更改,并且如果该对象被重新抛出,则将生效。
[编辑] 激活处理程序
当处理程序的参数(如果存在)初始化完成时,处理程序被认为是活动的。
此外,当因抛出异常而进入 std::terminate 时,隐式处理程序被认为是活动的。
当处理程序退出时,处理程序不再被认为是活动的。
具有最近激活且仍活动的处理程序的异常称为当前处理的异常。此类异常可以被重新抛出。
[编辑] 控制流
处理程序的 compound-statement 是一个控制流受限语句
void f() { goto label; // error try { goto label; // error } catch (...) { goto label: // OK label: ; } }
[编辑] 注意
堆栈展开发生在控制权转移到处理程序期间。当处理程序变为活动状态时,堆栈展开已经完成。
由 throw 0 的 throw 表达式抛出的异常不匹配指针或指向成员的指针类型的处理程序。
|
(C++11 起) |
异常对象永远不能具有数组或函数类型,因此对数组或函数类型的引用的处理程序永远不会匹配任何异常对象。
可以编写永远不会执行的处理程序,例如,将最终派生类的处理程序放置在相应明确公共基类的处理程序之后
try { f(); } catch (const std::exception& e) {} // will be executed if f() throws std::runtime_error catch (const std::runtime_error& e) {} // dead code!
许多实现过度扩展了 CWG 问题 388 的解决方案,使其适用于对非 const 指针类型的引用处理程序
int i; try { try { throw static_cast<float*>(nullptr); } catch (void*& pv) { pv = &i; throw; } } catch (const float* pf) { assert(pf == nullptr); // should pass, but fails on MSVC and Clang }
[编辑] 关键词
[编辑] 示例
以下示例演示了处理程序的几种用法
#include <iostream> #include <vector> int main() { try { std::cout << "Throwing an integer exception...\n"; throw 42; } catch (int i) { std::cout << " the integer exception was caught, with value: " << i << '\n'; } try { std::cout << "Creating a vector of size 5... \n"; std::vector<int> v(5); std::cout << "Accessing the 11th element of the vector...\n"; std::cout << v.at(10); // vector::at() throws std::out_of_range } catch (const std::exception& e) // caught by reference to base { std::cout << " a standard exception was caught, with message: '" << e.what() << "'\n"; } }
可能的输出
Throwing an integer exception... the integer exception was caught, with value: 42 Creating a vector of size 5... Accessing the 11th element of the vector... a standard exception was caught, with message: 'out_of_range'
[编辑] 缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 98 | C++98 | 一个 switch 语句可以将控制权转移到处理程序中 | 已禁止 |
CWG 210 | C++98 | throw 表达式与处理程序匹配 | 异常对象是 与处理程序匹配 |
CWG 388 | C++98 | 指针或指向成员的指针类型的异常可能 不匹配对不同类型的 const 引用 |
可匹配 当可转换时 |
CWG 1166 | C++98 | 当处理程序的类型是对抽象类类型的引用时,行为未指定 抽象类类型被匹配 |
抽象类类型 不允许用于处理程序 |
CWG 1769 | C++98 | 当处理程序的类型是异常对象类型的基类时,转换构造函数可能 用于处理程序参数的初始化 参数被复制初始化 |
从相应的基类 异常对象的子对象 异常对象的相应基类子对象 |
CWG 2093 | C++98 | 指向对象类型的异常对象无法通过限定符转换匹配指向对象类型的处理程序 指向对象类型的处理程序 |
允许 |
[编辑] 参考资料
- C++23 标准 (ISO/IEC 14882:2024)
- 14.4 处理异常 [except.handle]
- C++20 标准 (ISO/IEC 14882:2020)
- 14.4 处理异常 [except.handle]
- C++17 标准 (ISO/IEC 14882:2017)
- 18.3 处理异常 [except.handle]
- C++14 标准 (ISO/IEC 14882:2014)
- 15.3 处理异常 [except.handle]
- C++11 标准 (ISO/IEC 14882:2011)
- 15.3 处理异常 [except.handle]
- C++03 标准 (ISO/IEC 14882:2003)
- 15.3 处理异常 [except.handle]
- C++98 标准 (ISO/IEC 14882:1998)
- 15.3 处理异常 [except.handle]