throw 表达式

来自cppreference.com
< cpp‎ | language

对错误条件发信号,并执行错误处理。

目录

[编辑] 语法

throw expression (1)
throw (2)

[编辑] 解释

更多关于 trycatch (异常处理)块的信息见 try-catch 块
1) 首先,从 expression 复制初始化异常对象(这可能对右值表达式调用移动构造函数,而且复制/移动可能受到复制消除影响),然后转移控制给拥有匹配类型的异常处理,而其复合语句或成员初始化器列表为最近进入,且未从此线程的执行退出。即使复制初始化选择移动构造函数,从左值复制初始化也必须是两台的,且析构函数必须可访问 (C++14 起)
2) 重抛当前处理的异常。中止当前 catch 块的执行并将控制转移到下个匹配的异常处理(但不是到同一 try 块的下个 catch 子句:该复合语句被认为已经‘退出’),重用既存的异常对象:不生成新对象。此形式只在现在正在处理异常时允许(用于其他情况时调用 std::terminate )。关联到函数 try 块 的 catch 子句必须通过重抛出退出,若用于构造函数。

关于在异常处理期间引发错误,见 std::terminatestd::unexpected

[编辑] 异常对象

异常对象是 throw 表达式构造于未指定存储的临时对象。

异常对象的类型是 expression 除去顶层 cv 限定符的类型。数组与函数类型分别调整到指针和指向函数指针类型。若异常对象的类型将是不完整类型或指向异于(可有 cv 限定的) void 的不完整类型的指针,则该 throw 表达式导致编译时错误。若 expression 的类型为类类型,则其复制/移动构造函数和析构函数必须可访问,纵使发生复制消除

不同于其他临时对象,异常对象在初始化 catch 子句参数时被认为是左值,故它可用左值引用捕捉、修改及重抛。

异常对象持续到不因重抛退出的最后一条 catch 子句(若不因重抛退出,则它在 catch 子句的参数的析构后立即被销毁),或持续到引用此对象的最后一个 std::exception_ptr 被销毁(该情况下异常对象正好在 std::exception_ptr 的析构函数返回前被销毁)。

[编辑] 栈回溯

一旦构造好异常对象,控制流即反向作业(在调用栈向上)直至它抵达 try 块的起点,在该点按出现顺序比较所有关联的 catch 块参数和异常对象的类型,以找到匹配(此过程的细节见 try-catch )。若找不到匹配,则控制流继续回溯栈,直至下个 try 块,此后亦然。若找到匹配,则控制流跳到匹配的 catch 块。

因为控制流上移于调用栈,故它会为所有拥有自动存储期的已构造,但在进入对应的 try 块时未销毁的对象,以其构造函数完成的逆序调用构造函数。若异常从局部变量或用于 return 语句的临时量抛出,则从函数返回的对象的析构函数亦会得到调用。 (C++14 起)

若异常从构造函数或(稀有地)从析构函数抛出(不管该对象的存储期),则为全部完整构造的非静态、非变体 (C++14 前)成员和基类,以其构造函数完成的逆序调用析构函数。类联合体类的变体成员仅在从构造函数回溯的情况销毁,且若初始化与析构间的活动成员改变,则行为未定义。 (C++14 起)

若在非委托构造函数成功完成前,委托构造函数以异常退出,则调用此对象的析构函数。 (C++11 起)

若从 new 表达式所调用的构造函数抛出异常,则调用匹配的解分配函数,若它可用。

此过程被称为栈回溯

若任何直接由栈回溯机制调用的函数,在异常对象初始化后、异常处理的起始前,以异常退出,则调用 std::terminate 。这种函数包括退出作用域的拥有自动存储期对象的析构函数,和为初始化以值捕捉的参数,而调用(若未被消除)的复制构造函数。

若抛出异常且不捕捉,包括退出 std::thread 的函数、 main 函数,及任何静态或线程局域对象的构造函数或析构函数的作用域,则调用 std::terminate 。对于未捕捉异常是否发生任何栈回溯是实现定义的。

[编辑] 注意

在重抛异常时,必须使用第二形式以避免异常对象使用继承的(典型)情况中的对象切片:

try {
    std::string("abc").substr(10); // 抛出 std::length_error
} catch(const std::exception& e) {
    std::cout << e.what() << '\n';
//  throw e; // 复制初始化一个 -exception 类型的新异常对象
    throw;   // 重抛拥有 std::length_error 类型的异常对象
}

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

报告 适用于 已发布的行为 正确行为
CWG 1866 C++14 从构造函数栈回溯时会泄露变体成员 变体成员被销毁
CWG 1863 C++14 在抛出时对仅移动异常对象不要求复制构造函数,但允许之后复制 要求复制构造函数
CWG 2176 C++14 从局部变量析构函数抛出可以跳过返回值析构函数 添加函数返回值到回溯

[编辑] 参阅