命名空间
变体
操作

抛出异常

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
 
 
异常
try
抛出异常
处理异常
异常规范
    noexcept 规范 (C++11)
    动态规范 (直到 C++17*)
noexcept 运算符 (C++11)
 

抛出 异常 会将控制权转移到 处理程序.

可以从 throw 表达式 中抛出异常,以下上下文也可能会抛出异常

内容

[编辑] 异常对象

抛出异常会使用动态 存储期限 初始化一个对象,称为异常对象

如果异常对象的类型是以下类型之一,则程序格式不正确

[编辑] 构造和析构异常对象

假设异常对象的类型为 T

  • obj 为类型为 const T 的左值,从 obj 对类型为 T 的对象的 复制初始化 必须是格式良好的。
  • 如果 T 是类类型

异常对象的内存以未指定的方式分配。唯一的保证是该存储永远不会通过全局 分配函数 进行分配。

如果一个 处理程序 通过 重新抛出 退出,控制权将传递给同一异常对象的另一个处理程序。在这种情况下,异常对象不会被销毁。

当异常的最后一个活动处理程序以任何方式(重新抛出除外)退出时,异常对象将被销毁,并且实现可能会以未指定的方式释放临时对象的内存。

销毁发生在处理程序中“参数列表”中声明的对象销毁后立即发生。

(直到 C++11)

异常对象的潜在销毁点是

  • 当异常的活动处理程序以任何方式(重新抛出除外)退出时,在处理程序中“参数列表”中声明的对象(如果有)销毁后立即发生。
  • 当引用异常对象的 std::exception_ptr 类型对象被销毁时,在 std::exception_ptr 的析构函数返回之前。

在异常对象的潜在销毁点中,有一个未指定的最后一个点,在那里异常对象被销毁。所有其他点 发生在 那个最后点之前。然后,实现可能会以未指定的方式释放异常对象的内存。

(自 C++11 起)

[编辑] throw 表达式

throw 表达式 (1)
throw (2)
1) 抛出一个新的异常。
2) 重新抛出当前正在处理的异常。
表达式 - 用于构造异常对象的表达式


当抛出一个新的异常时,其异常对象将按如下方式确定:

  1. 表达式 执行 数组到指针函数到指针 标准转换。
  2. ex 为转换结果
  • 异常对象的类型通过从 ex 的类型中移除任何顶层 cv 限定符来确定。
  • 异常对象是 复制初始化 的,来自 ex.

如果程序在当前没有处理异常的情况下尝试重新抛出异常,则将调用 std::terminate。否则,将使用现有的异常对象(不会创建新的异常对象)重新激活异常,并且该异常不再被视为已捕获。

try
{
    // throwing a new exception 123
    throw 123;
}
catch (...) // catch all exceptions
{
    // respond (partially) to exception 123
    throw; // pass the exception to some other handler
}

[编辑] 堆栈展开

一旦构造了异常对象,控制流将向后(向上遍历调用堆栈)工作,直到它到达一个 try 的开头,此时将按出现的顺序比较所有相关处理程序的参数与异常对象的类型,以找到一个 匹配项。如果找不到匹配项,控制流将继续展开堆栈,直到下一个 try 块,依此类推。如果找到匹配项,控制流将跳转到匹配的处理程序。

当控制流向上遍历调用堆栈时,将为所有具有 自动存储持续时间 且已构造但尚未销毁的对象调用析构函数,按照其构造函数完成的相反顺序。如果从局部变量的析构函数或在 return 语句中使用的临时对象的析构函数中抛出异常,则还会调用从函数返回的对象的析构函数。

如果从对象的构造函数或(罕见)析构函数中抛出异常(无论对象的存储持续时间如何),则将为所有已完全构造的非静态非变体成员和基类调用析构函数,按照其构造函数完成的相反顺序。 类似联合的类 的变体成员仅在从构造函数展开的情况下才被销毁,如果活动成员在初始化和销毁之间发生了变化,则行为未定义。

如果委托构造函数在非委托构造函数成功完成之后以异常退出,则将调用此对象的析构函数。

(自 C++11 起)

如果从由 new 表达式调用的构造函数中抛出异常,则将调用匹配的 释放函数(如果可用)。

这个过程被称为堆栈展开

如果堆栈展开机制在异常对象初始化之后和异常处理程序开始之前直接调用的任何函数以异常退出,则将调用 std::terminate。这些函数包括具有自动存储持续时间的对象的 析构函数(其范围已退出),以及用于初始化按值捕获的参数的异常对象的复制构造函数(如果未 省略)。

如果抛出一个异常且未捕获,包括从 std::thread 的初始函数、main 函数以及任何静态或线程局部对象的构造函数或析构函数中逃逸的异常,则将调用 std::terminate。对于未捕获的异常,是否进行任何堆栈展开是实现定义的。

[编辑] 注释

重新抛出异常时,必须使用第二种形式来避免在异常对象使用继承的(典型)情况下发生对象切片。

try
{
    std::string("abc").substr(10); // throws std::out_of_range
}
catch (const std::exception& e)
{
    std::cout << e.what() << '\n';
//  throw e; // copy-initializes a new exception object of type std::exception
    throw;   // rethrows the exception object of type std::out_of_range
}

throw 表达式被归类为 右值表达式,类型为 void。像任何其他表达式一样,它也可能是另一个表达式的子表达式,最常见的是在 条件运算符 中。

double f(double d)
{
    return d > 1e7 ? throw std::overflow_error("too big") : d;
}
 
int main()
{
    try
    {
        std::cout << f(1e10) << '\n';
    }
    catch (const std::overflow_error& e)
    {
        std::cout << e.what() << '\n';
    }
}

[编辑] 关键字

throw

[编辑] 示例

#include <iostream>
#include <stdexcept>
 
struct A
{
    int n;
 
    A(int n = 0): n(n) { std::cout << "A(" << n << ") constructed successfully\n"; }
    ~A() { std::cout << "A(" << n << ") destroyed\n"; }
};
 
int foo()
{
    throw std::runtime_error("error");
}
 
struct B
{
    A a1, a2, a3;
 
    B() try : a1(1), a2(foo()), a3(3)
    {
        std::cout << "B constructed successfully\n";
    }
    catch(...)
    {
        std::cout << "B::B() exiting with exception\n";
    }
 
    ~B() { std::cout << "B destroyed\n"; }
};
 
struct C : A, B
{
    C() try
    {
        std::cout << "C::C() completed successfully\n";
    }
    catch(...)
    {
        std::cout << "C::C() exiting with exception\n";
    }
 
    ~C() { std::cout << "C destroyed\n"; }
};
 
int main () try
{
    // creates the A base subobject
    // creates the a1 member of B
    // fails to create the a2 member of B
    // unwinding destroys the a1 member of B
    // unwinding destroys the A base subobject
    C c;
}
catch (const std::exception& e)
{
    std::cout << "main() failed to create C with: " << e.what();
}

输出

A(0) constructed successfully
A(1) constructed successfully
A(1) destroyed
B::B() exiting with exception
A(0) destroyed
C::C() exiting with exception
main() failed to create C with: error

[编辑] 缺陷报告

以下更改行为的缺陷报告被追溯地应用于先前发布的 C++ 标准。

DR 应用于 已发布的行为 正确的行为
CWG 499 C++98 无法抛出具有未知边界的数组,因为
其类型不完整,但可以从已衰减的指针创建异常对象,没有任何问题
将类型完成
要求应用于
异常对象,而不是
异常对象,而不是
CWG 668 C++98 如果从局部非自动对象的析构函数中抛出异常,则不会调用 std::terminate
从局部非自动对象的析构函数中抛出异常
在这种情况下调用 std::terminate
在这种情况下调用 std::terminate
CWG 1863 C++11 当抛出时,不需要为仅移动的异常对象提供复制构造函数,但之后允许复制
需要复制构造函数
需要复制构造函数
CWG 1866 C++98 在从构造函数展开堆栈时,变体成员会泄漏 变体成员被销毁
CWG 2176 C++98 从局部变量析构函数中抛出异常
可能会跳过返回值析构函数
函数返回值
已添加到展开中
CWG 2699 C++98 throw "EX" 实际上会抛出 char* 而不是 const char* 已更正
CWG 2711 C++98 没有指定异常对象的复制初始化的来源
表达式 复制初始化
表达式 复制初始化
表达式 复制初始化
CWG 2775 C++98 异常对象的复制初始化要求不清楚 已澄清
CWG 2854 C++98 异常对象的存储持续时间不清楚 已澄清
P1825R0 C++11 throw 中禁止从参数隐式移动 允许

[编辑] 参考文献

  • C++23 标准 (ISO/IEC 14882:2024)
  • 7.6.18 抛出异常 [expr.throw]
  • 14.2 抛出异常 [except.throw]
  • C++20 标准 (ISO/IEC 14882:2020)
  • 7.6.18 抛出异常 [expr.throw]
  • 14.2 抛出异常 [except.throw]
  • C++17 标准 (ISO/IEC 14882:2017)
  • 8.17 抛出异常 [expr.throw]
  • 18.1 抛出异常 [except.throw]
  • C++14 标准 (ISO/IEC 14882:2014)
  • 15.1 抛出异常 [except.throw]
  • C++11 标准 (ISO/IEC 14882:2011)
  • 15.1 抛出异常 [except.throw]
  • C++03 标准 (ISO/IEC 14882:2003)
  • 15.1 抛出异常 [except.throw]
  • C++98 标准 (ISO/IEC 14882:1998)
  • 15.1 抛出异常 [except.throw]

[编辑] 另请参阅

(直到 C++17)