处理异常
一个 异常 可以由一个处理器处理。
目录 |
[编辑] 处理器
catch ( attr (可选) 类型说明符序列 声明符 ) 复合语句 |
(1) | ||||||||
catch ( attr (可选) 类型说明符序列 抽象声明符 (可选) ) 复合语句 |
(2) | ||||||||
catch ( ... ) 复合语句 |
(3) | ||||||||
attr | - | (自 C++11 起) 任意数量的 属性,适用于参数 |
类型说明符序列 | - | 形式参数声明的一部分,与函数 参数列表 中相同 |
声明符 | - | 参数声明的一部分,与函数 参数列表 中相同 |
抽象声明符 | - | 未命名参数声明的一部分,与函数 参数列表 中相同 |
复合语句 | - | 一个 复合语句 |
处理器中的参数声明描述了可以导致该处理器被进入的异常类型。
如果参数声明为以下类型之一,则程序格式错误
|
(自 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 (...) 处理器匹配任何类型的异常。如果存在,它只能是处理器序列中的最后一个处理器。此处理器可用于确保任何未捕获的异常都不能从提供 nothrow 异常保证 的函数中逃逸。
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 对非常量指针类型引用的处理程序的解析。
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]