动态异常声明 (C++17 前)
列出函数可能直接或间接抛出的异常。
目录 |
[编辑] 语法
throw( 类型 ID 列表(可选)) |
(1) | (C++11 中已弃用) (C++17 中已移除) | |||||||
类型 ID 列表 | - | 逗号分隔的 类型 ID 列表,代表包展开的类型 ID 后跟省略号 (...)(自 C++11 起) |
显式动态异常声明应仅出现在函数声明符中,用于函数类型、函数指针类型、函数引用类型或成员函数指针类型,该类型是声明或定义的顶层类型,或作为函数声明符中的形参或返回类型出现的此类类型。
void f() throw(int); // OK: function declaration void (*pf)() throw (int); // OK: pointer to function declaration void g(void pfa() throw(int)); // OK: pointer to function parameter declaration typedef int (*pf)() throw(int); // Error: typedef declaration
[编辑] 解释
如果函数声明的动态异常声明中列出了类型 T
,则该函数可能会抛出该类型或从该类型派生的类型的异常。
不完整类型、指向或引用 cv void*
以外的不完整类型的指针或引用,以及右值引用类型(自 C++11 起) 在异常声明中是不允许的。数组和函数类型(如果使用)将调整为相应的指针类型,顶层 cv 限定符也会被删除。形参包 是允许的(自 C++11 起)。
调整后的类型集为空的动态异常声明(在任何包展开后)(自 C++11 起) 是非抛出的。具有非抛出动态异常声明的函数不允许任何异常。
动态异常声明不被视为函数类型的一部分。
如果函数抛出异常,但其类型未在其异常声明中列出,则会调用函数 std::unexpected。默认函数调用 std::terminate,但它可以被用户提供的函数(通过 std::set_unexpected)替换,后者可以调用 std::terminate 或抛出异常。如果从 std::unexpected 抛出的异常被异常声明接受,则堆栈展开照常继续。如果未被接受,但异常声明允许 std::bad_exception,则会抛出 std::bad_exception。否则,将调用 std::terminate。
[编辑] 实例化
函数模板特化的动态异常声明不会与函数声明一起实例化;它仅在需要时(如下定义)实例化。
隐式声明的特殊成员函数的动态异常声明也仅在需要时进行评估(特别是,派生类的成员函数的隐式声明不需要基类成员函数的异常声明被实例化)。
当函数模板特化的动态异常声明是需要的,但尚未实例化时,将查找依赖名称,并且在 表达式 中使用的任何模板都将实例化,就像为了特化的声明一样。
在以下上下文中,函数的动态异常声明被认为是需要的
- 在表达式中,函数通过重载解析选择
- 该函数被 odr-使用
- 该函数本应被 odr-使用,但出现在未求值操作数中
template<class T> T f() throw(std::array<char, sizeof(T)>); int main() { decltype(f<void>()) *p; // f unevaluated, but exception specification is needed // error because instantiation of the exception specification // calculates sizeof(void) }
- 需要该声明以与另一个函数声明进行比较(例如,在虚函数覆盖器上或函数模板的显式特化上)
- 在函数定义中
- 需要该声明,因为默认的特殊成员函数需要检查它,以便决定其自身的异常声明(这仅在默认的特殊成员函数的声明本身是需要时发生)。
[编辑] 潜在异常
每个函数 f
、函数指针 pf
和成员函数指针 pmf
都有一个潜在异常集,其中包含可能抛出的类型。所有类型的集合表示可能抛出任何异常。此集合定义如下
f
、pf
或 pmf
的声明使用动态异常声明,且不允许所有异常(C++11 前),则该集合由该声明中列出的类型组成。(自 C++11 起) |
注意:对于隐式声明的特殊成员函数(构造函数、赋值运算符和析构函数)以及对于继承构造函数(自 C++11 起),潜在异常集是它们将调用的所有内容的潜在异常集的组合:非变体非静态数据成员、直接基类和(在适当情况下)虚基类的构造函数/赋值运算符/析构函数(包括默认实参表达式,始终如此)。
每个表达式 e
都有一个潜在异常集。如果 e
是一个 核心常量表达式,则该集合为空,否则,它是 e
的所有直接子表达式(包括 默认实参表达式)的潜在异常集的并集,再结合另一个取决于 e
形式的集合,如下所示
e
是函数调用表达式,令 g
表示被调用的函数、函数指针或成员函数指针,则- 如果
g
的声明使用动态异常声明,则g
的潜在异常集被添加到该集合中;
- 如果
|
(自 C++11 起) |
- 否则,该集合是所有类型的集合。
(自 C++11 起) |
void f() throw(int); // f()'s set is "int" void g(); // g()'s set is the set of all types struct A { A(); }; // "new A"'s set is the set of all types struct B { B() noexcept; }; // "B()"'s set is empty struct D() { D() throw (double); }; // new D's set is the set of all types
所有隐式声明的成员函数和继承构造函数(自 C++11 起)都有异常声明,选择如下
- 如果潜在异常集是所有类型的集合,则隐式异常声明允许所有异常(即使在代码中无法表达且行为如同没有异常声明,异常声明也被认为是存在的)(C++11 前)是 noexcept(false)(自 C++11 起)。
- 否则,如果潜在异常集非空,则隐式异常声明列出集合中的每种类型。
- 否则,隐式异常声明是 throw()(C++11 前)noexcept(true)(自 C++11 起)。
struct A { A(int = (A(5), 0)) noexcept; A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) = default; // exception specification is "noexcept(true)" B(B&&, int = (throw Y(), 0)) noexcept; ~B() throw(Y); }; int n = 7; struct D : public A, public B { // May throw an exception of a type that would match a handler of type // std::bad_array_new_length, but does not throw a bad allocation exception (void*) new (std::nothrow) int[n]; // D may have the following implicitly-declared members: // D::D() throw(X, std::bad_array_new_length); // D::D(const D&) noexcept(true); // D::D(D&&) throw(Y); // D::~D() throw(X, Y); };
[编辑] 注解
Clang 认为 C++11 中 CWG1330 更改了动态异常声明的实例化规则,请参阅 LLVM #56349。
[编辑] 关键词
[编辑] 示例
注意:最好在 C++98 模式下编译以避免警告。与 C++17 及更高版本不兼容。
#include <cstdlib> #include <exception> #include <iostream> class X {}; class Y {}; class Z : public X {}; class W {}; void f() throw(X, Y) { bool n = false; if (n) throw X(); // OK, would call std::terminate() if (n) throw Z(); // also OK throw W(); // will call std::unexpected() } void handler() { std::cerr << "That was unexpected!\n"; // flush needed std::abort(); } int main() { std::set_unexpected(handler); f(); }
输出
That was unexpected!
[编辑] 缺陷报告
以下行为变更缺陷报告已追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 25 | C++98 | 具有不同异常声明的成员指针之间的赋值和初始化行为是未指定的 具有不同 异常声明的成员指针之间的赋值和初始化行为是未指定的 |
应用限制 对于函数指针 和引用 |
CWG 973 | C++98 | 异常声明可能包含函数类型,但未指定相应的函数指针转换 相应的函数指针转换未指定 |
已指定 |
CWG 1330 | C++98 | 异常声明可能会被急切地实例化 | 仅在需要时才实例化 |
CWG 1267 | C++11 | 异常声明中允许右值引用类型 | 不允许 |
CWG 1351 | C++98 C++11 |
默认实参(C++98)和默认成员初始化器 (C++11)在隐式异常声明中被忽略 |
被考虑 |
CWG 1777 | C++11 | throw(T...) 即使 T 是空包也不是非抛出声明 即使 T 是空包也不是非抛出 |
它是非抛出的 如果包为空 |
CWG 2191 | C++98 | typeid 表达式的潜在异常集可能包含 bad_typeid ,即使它不能被抛出typeid 表达式的潜在异常集可能包含 bad_typeid ,即使它不能被抛出 |
包含 bad_typeid 仅当它可以被抛出时 |
[编辑] 参见
noexcept 说明符(C++11) |
指定函数是否可能抛出异常 |