处理异常
异常可以由处理程序处理。
目录 |
[[编辑]] 处理程序
catch ( attr (可选) 类型说明符序列 声明符 ) 复合语句 |
(1) | ||||||||
catch ( attr (可选) 类型说明符序列 抽象声明符 (可选) ) 复合语句 |
(2) | ||||||||
catch ( ... ) 复合语句 |
(3) | ||||||||
attr | - | (自 C++11 起) 任意数量的 属性,应用于形参 |
type-specifier-seq | - | 形式形参声明的一部分,与函数 形参列表 中相同 |
declarator | - | 形参声明的一部分,与函数 形参列表 中相同 |
abstract-declarator | - | 无名形参声明的一部分,与函数 形参列表 中相同 |
compound-statement | - | 复合语句 |
处理程序中的形参声明描述了可以导致进入该处理程序的异常类型。
如果形参声明为以下类型之一,则程序是非良构的
|
(自 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 时,隐式处理程序也被认为是活动的。
当处理程序退出时,不再认为处理程序是活动的。
最近激活且仍处于活动状态的处理程序的异常称为当前处理的异常。这样的异常可以被重新抛出。
[[编辑]] 控制流
处理程序的 复合语句 是控制流限制语句
void f() { goto label; // error try { goto label; // error } catch (...) { goto label: // OK label: ; } }
[[编辑]] 注解
栈回溯发生在控制权转移到处理程序时。当处理程序变为活动状态时,栈回溯已经完成。
由 throw 表达式 throw 0 抛出的异常与指针或指向成员类型的指针的处理程序不匹配。
|
(自 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++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
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]