处理异常

来自cppreference.com
< cpp‎ | language


 
 
C++ 语言
 
 
异常
try
抛出异常
处理异常
异常说明
    noexcept 说明 (C++11)
    动态说明 (C++17 前*)
noexcept 运算符 (C++11)
 

异常可以由处理块进行处理。

处理块

catch ( 属性 (可选) 类型说明符序列 声明符 ) 复合语句 (1)
catch ( 属性 (可选) 类型说明符序列 抽象声明符 (可选) ) 复合语句 (2)
catch ( ... ) 复合语句 (3)
1) 带有一个具名形参的处理块。
2) 带有一个无名形参的处理块。
3) 可以匹配所有异常的处理块。
属性 - (C++11 起) 任意数量的属性,应用到形参
类型说明符序列 - 形参声明的一部分,与在函数形参列表中相同
声明符 - 形参声明的一部分,与在函数形参列表中相同
抽象声明符 - 无名形参声明的一部分,与在函数形参列表中相同
复合语句 - 复合语句


处理块中的形参声明描述了可以导致进入该处理块的异常类型。

如果形参声明为具有以下类型之一,那么程序非良构:

(C++11 起)
  • 指向(可有 cv 限定的)void 以外的不完整类型的指针
  • 到不完整类型的左值引用

如果形参声明为具有函数类型 T 或类型“T 的数组”,那么该类型会被调整到“指向 T 的指针”。

具有形参类型 T 的处理块可以简称为“T 类型处理块”。

匹配异常

每个 try都与若干处理块关联,这些处理块组成了一个处理块序列。当 try 块中有异常抛出时,会按出现顺序对序列中的每个处理块匹配异常。

满足以下任意条件的处理块会匹配 E 类型的异常对象

  • 处理块具有类型“可有 cv 限定的 T” 或“到可有 cv 限定的 T 的左值引用”,并且满足以下任意条件:
  • ET(在忽略顶层 cv 限定的情况下)是相同的类型。
  • TE 的无歧义公开基类。
  • 处理块具有类型“可有 cv 限定的 T” 或 const T&,其中 T 是指针或成员指针类型,并且满足以下任意条件:
  • E 是可以通过以下至少一种转换转换到 T 的指针或成员指针类型:
  • 不涉及到指向有歧义类的成员或类的私有或受保护成员的指针的转换的标准指针转换
(C++17 起)
(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 块的所有处理块中没有匹配的处理块,那么会对相同线程 (C++11 起)的动态外围 try 块继续查找匹配的处理块。

如果没有找到匹配的处理块,那么就会调用 std::terminate;由实现定义是否会在该 std::terminate 调用前进行栈回溯

处理异常

在抛出异常时,控制会转移到具有匹配类型的最近处理块;这里“最近”表示控制线程最近进入且尚未退出的 try 关键词之后的符合语句或成员初始化器列表(如果存在)对应的最近处理块。

初始化处理块形参

形参列表中声明的具有类型“可有 cv 限定的 T”或“到可有 cv 限定的 T 的左值引用”的形参(如果存在)会按以下方式从 E 类型的异常对象初始化:

  • 如果 TE 的基类,那么形参会从指定了异常对象的对应基类子对象的 T 类型左值复制初始化
  • 否则形参会从指定了异常对象的 E 类型左值复制初始化。

形参的生存期会在处理块退出时,并在处理块中初始化的所有具有自动存储期的对象析构后结束。

当声明形参为对象时,修改该对象不会影响异常对象。

当声明形参为到对象的引用时,修改被引用对象就是修改异常对象,并且在重新抛出该对象时也会生效。

激活处理块

在处理块的形参(如果存在)的初始化完成时,该处理块进入活跃 状态。

另外,因抛出异常而进入 std::terminate 时会将一个隐式的处理块进入活跃状态。

处理块在退出时不再视为处于活跃状态。

最近激活且处于活跃状态中的处理块匹配的异常被称为当前正在处理的异常。这种异常可以重新抛出

控制流

处理块的复合语句 是有控制流限制的语句

void f()
{
    goto label;     // 错误
    try
    {
        goto label; // 错误
    }
    catch (...)
    {
        goto label: // OK
        label: ;
    }
}

注解

栈回溯会在转移控制到处理块的过程中发生。当处理块进入活跃状态时,栈回溯已经完成。

throw 表达式 throw 0 抛出的异常不会匹配指针或成员指针类型的处理块。

  • 需要匹配此类处理块时可以改用 throw nullptr 抛出空指针。
(C++11 起)

因为异常对象无法具有数组或函数类型,所以到数组或函数的引用类型的处理块不会匹配任何异常对象。

有可能会写出永远无法执行的处理块,比如将最终派生类型的处理块放在对应的无歧义公开基类的处理块后面:

try
{
    f();
}
catch (const std::exception& e)
{} // 在 f() 抛出 std::runtime_error 时执行
catch (const std::runtime_error& e)
{} // 死代码!

许多实现将 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); // 应该通过,但在 MSVC 与 Clang 上失败
}

关键词

catch

示例

以下代码演示处理块的几种用法:

#include <iostream>
#include <vector>
 
int main()
{
    try
    {
        std::cout << "抛出整数异常...\n";
        throw 42;
    }
    catch (int i)
    {
        std::cout << "  整数异常已捕获,它的值是:" << i << '\n';
    }
 
    try
    {
        std::cout << "创建一个大小为 5 的 vector...\n";
        std::vector<int> v(5);
        std::cout << "访问 vector 的第 11 个元素...\n";
        std::cout << v.at(10); // vector::at() 会抛出 std::out_of_range
    }
    catch (const std::exception& e) // 按基类的引用捕获
    {
        std::cout << "  标准异常已捕获,它的信息是:'" << e.what() << "'\n";
    }
 
}

可能的输出:

抛出整数异常...
  整数异常已捕获,它的值是:42
创建一个大小为 5 的 vector...
访问 vector 的第 11 个元素...
  标准异常已捕获,它的信息是:'out_of_range'

缺陷报告

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

缺陷报告 应用于 出版时的行为 正确行为
CWG 98 C++98 switch 语句可以转移控制进入处理块 已禁止
CWG 210 C++98 会以 throw 表达式来匹配处理块 会以异常对象来匹配处理块
CWG 388 C++98 指针或成员指针类型的异常不能为到不同类型的 const 引用匹配 使之在可转换时可匹配
CWG 1166 C++98 未指明匹配到异常类型是到抽象类的引用类型的处理块时的行为 catch 子句不能对应抽象类类型
CWG 1769 C++98 当 catch 子句声明的异常类型是异常对象的基类时,
该 处理块的形参的初始化可能会用到转换构造函数
此时该形参会从异常对象的
对应基类子对象复制初始化
CWG 2093 C++98 具有成员指针类型的异常对象无法通过限定性转换匹配具有成员指针类型的处理块 可以匹配

引用

  • C++23 标准(ISO/IEC 14882:2024):
  • 14.4 Handling an exception [except.handle]
  • C++20 标准(ISO/IEC 14882:2020):
  • 14.4 Handling an exception [except.handle]
  • C++17 标准(ISO/IEC 14882:2017):
  • 18.3 Handling an exception [except.handle]
  • C++14 标准(ISO/IEC 14882:2014):
  • 15.3 Handling an exception [except.handle]
  • C++11 标准(ISO/IEC 14882:2011):
  • 15.3 Handling an exception [except.handle]
  • C++03 标准(ISO/IEC 14882:2003):
  • 15.3 Handling an exception [except.handle]
  • C++98 标准(ISO/IEC 14882:1998):
  • 15.3 Handling an exception [except.handle]

参阅