Handling exceptions
一个 异常 可以被处理器捕获处理。
目录 |
处理器
catch
(
attr
(可选)
type-specifier-seq
declarator
)
compound-statement
|
(1) | ||||||||
catch
(
attr
(可选)
type-specifier-seq
abstract-declarator
(可选)
)
compound-statement
|
(2) | ||||||||
catch
(
...
)
compound-statement
|
(3) | ||||||||
| attr | - | (since 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:
-
- 不涉及指向私有、受保护或歧义类的指针转换的 标准指针转换 。
-
|
(since C++17) |
-
-
- 一个 限定转换 。
-
|
(since C++11) |
catch ( ... ) 处理程序可匹配任何类型的异常。如果存在,它只能是处理程序序列中的最后一个处理程序。该处理程序可用于确保没有任何未捕获的异常能够从提供 不抛出异常保证 的函数中逸出。
try { f(); } catch (const std::overflow_error& e) {} // 当f()抛出std::overflow_error时执行此块(同类型匹配规则) catch (const std::runtime_error& e) {} // 当f()抛出std::underflow_error时执行此块(基类匹配规则) catch (const std::exception& e) {} // 当f()抛出std::logic_error时执行此块(基类匹配规则) catch (...) {} // 当f()抛出std::string或int等其他无关类型时执行此块
如果在 try 块的处理器中未找到匹配项,则会在同一线程的动态外围 try 块 中继续搜索匹配的处理器 (C++11 起) 。
如果未找到匹配的处理器,将调用 std::terminate ;在此调用 std::terminate 之前是否进行 栈展开 由实现定义。
异常处理
当异常被抛出时,控制权会转移至具有匹配类型的最近处理程序;"最近"指的是该处理程序对应的复合语句或成员初始化列表(如果存在)在控制线程中最近进入且尚未退出的 try 关键字之后的部分。
初始化处理程序参数
参数列表中声明的参数(如果有),其类型为“可能带有 cv 限定符的
T
”或“可能带有 cv 限定符的
T
的左值引用”,将按以下方式从类型为
E
的
异常对象
进行初始化:
-
若
T是E的基类,则参数通过 复制初始化 从类型为T的左值(指向异常对象的对应基类子对象)进行初始化。 -
否则,参数通过从类型为
E的左值(指向异常对象)进行复制初始化。
形参的生存期在处理程序退出时结束,此时处理程序内初始化的具有自动 存储期 的对象均已析构完成。
当参数被声明为对象时,对该对象的任何修改都不会影响异常对象。
当参数被声明为对象引用时,对引用对象的任何修改即是对异常对象的修改,若该对象被重新抛出,这些修改将会生效。
激活处理程序
当处理器的参数(如果有)初始化完成时,该处理器被视为 活跃 状态。
此外,当由于抛出异常而进入 std::terminate 时,隐式处理程序被视为处于活动状态。
当处理程序退出时,它不再被视为活动状态。
最近激活且仍处于活动状态的异常处理程序所对应的异常称为 当前处理中的异常 。此类异常可被 重新抛出 。
控制流
处理器的 复合语句 是一个 控制流受限语句 :
void f() { goto label; // 错误 try { goto label; // 错误 } catch (...) { goto label: // 正确 label: ; } }
注释
Stack unwinding 在控制权转移至异常处理程序时发生。当处理程序被激活时,栈展开过程已经完成。
由 throw 表达式 throw 0 抛出的异常与指针或指向成员类型的处理器不匹配。
|
(since C++11) |
异常对象 永远不能具有数组或函数类型,因此对数组或函数类型的引用处理程序永远不会匹配任何异常对象。
可以编写永远不会被执行的处理程序,例如将最终派生类的处理程序放置在对应的明确公共基类处理程序之后:
try { f(); } catch (const std::exception& e) {} // 当f()抛出std::runtime_error时将被执行 catch (const std::runtime_error& e) {} // 死代码!
许多实现过度扩展了 CWG issue 388 的解析范围,将其应用于非常量指针类型的引用处理程序:
int i; try { try { throw static_cast<float*>(nullptr); } catch (void*& pv) { pv = &i; throw; } } catch (const float* pf) { assert(pf == nullptr); // 预期通过,但在 MSVC 和 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 |
指针或成员指针类型的异常
无法通过常量引用匹配到不同类型 |
在可转换时
允许匹配 |
| 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]