命名空间
变体
操作

抛出异常

来自 cppreference.cn
< 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 expression (1)
throw (2)
1) 抛出一个新异常。
2) 重新抛出当前正在处理的异常。
表达式 - 用于构造异常对象的表达式


当抛出新异常时,其异常对象确定如下

  1. expression 执行数组到指针函数到指针标准转换。
  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 块,依此类推。如果找到匹配项,控制流将跳转到匹配的处理程序。

随着控制流向上调用栈,所有具有自动存储期且在相应的try 块进入后已构造但尚未销毁的对象,其析构函数将按构造函数完成的逆序被调用。如果从局部变量的析构函数或在return语句中使用的临时变量的析构函数中抛出异常,则还会调用从函数返回的对象的析构函数。

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

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

(C++11 起)

如果异常是从new-expression调用的构造函数中抛出的,则如果可用,会调用匹配的解除分配函数

此过程称为栈展开

如果在异常对象初始化之后、异常处理程序开始之前,任何由栈展开机制直接调用的函数以异常退出,则会调用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 表达式被分类为类型为voidprvalue 表达式。与任何其他表达式一样,它可能是另一个表达式中的子表达式,最常见的是在条件运算符

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++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
CWG 499 C++98 未知边界的数组不能被抛出,因为
其类型不完整,但异常对象可以
从衰减的指针创建,没有任何问题
将类型完成要求
应用于异常对象
代替
CWG 668 C++98 如果从局部非自动对象的析构函数中抛出异常,则不调用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 未指定异常对象的复制初始化源
异常对象未指定
expression
复制初始化
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 前)