Namespaces
Variants

Dynamic exception specification (until C++17)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous
Exceptions
try block
Throwing exceptions
Handling exceptions
Exception specification
noexcept specification (C++11)
dynamic specification ( until C++17* )
noexcept operator (C++11)

列出函数可能直接或间接抛出的异常。

目录

语法

throw( 类型标识列表  (可选) ) (1) (C++11 中弃用)
(C++17 中移除)
1) 显式动态异常规范。
type-id-list - 以逗号分隔的 类型标识 列表 ,表示 包展开 的类型标识后接省略号 (...) (C++11 起)

显式动态异常规范应仅出现在函数类型、指向函数类型的指针、引用函数类型或指向成员函数类型的指针的声明符上,这些类型必须是声明或定义的顶层类型,或者作为函数声明符中的参数或返回类型出现。

void f() throw(int);            // 正确:函数声明
void (*pf)() throw (int);       // 正确:函数指针声明
void g(void pfa() throw(int));  // 正确:函数指针参数声明
typedef int (*pf)() throw(int); // 错误:typedef声明

说明

如果函数在其动态异常规范中声明了类型 T ,则该函数可能抛出该类型或其派生类型的异常。

不完整类型 、指向不完整类型的指针或引用(cv void* 除外) 以及右值引用类型 (C++11 起) 不允许出现在异常规范中。若使用数组和函数类型,会被调整为对应指针类型,同时顶层 cv 限定符也会被移除。 允许使用 形参包 (C++11 起)

动态异常规范当其调整类型集合为空时 (在任何包展开后) (since 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 未求值,但需要异常规范
                            // 错误:因为实例化异常规范时
                            // 需要计算 sizeof(void)
}
  • 需要规范来与另一个函数声明进行比较(例如,在虚函数重写或函数模板显式特化时)
  • 在函数定义中
  • 需要规范是因为默认的特殊成员函数需要检查它,以决定自身的异常规范(这仅在默认特殊成员函数本身的规范被需要时发生)。

潜在异常

每个函数 f 、函数指针 pf 以及成员函数指针 pmf 都具有一个 潜在异常集合 ,该集合包含可能被抛出的类型。所有类型的集合表示可能抛出任何异常。该集合的定义如下:

1) f pf pmf 的声明使用了不允许所有异常的动态异常规范 that does not allow all exceptions (until C++11) ,则该集合由该规范中列出的类型构成。
2) 否则,若 f pf pmf 的声明使用了 noexcept(true) ,则该集合为空。
(since C++11)
3) 否则,该集合为所有类型的集合。

注意:对于隐式声明的特殊成员函数(构造函数、赋值运算符和析构函数) 以及继承构造函数 (C++11 起) ,其潜在异常集合由它们所调用操作的全部潜在异常组合而成:包括非变体非静态数据成员、直接基类以及(若适用)虚基类的构造函数/赋值运算符/析构函数(一如既往地包含默认参数表达式)。

每个表达式 e 都有一个 潜在异常集合 。若 e 核心常量表达式 ,则该集合为空;否则,该集合是 e 的所有直接子表达式(包括 默认参数表达式 )的潜在异常集合的并集,再与另一个取决于 e 形式的集合组合而成,具体如下:

1) e 是函数调用表达式,令 g 表示被调用的函数、函数指针或成员函数指针,则
  • g 的声明使用动态异常规范,则将 g 的潜在异常集合加入当前集合;
(since C++11)
  • 否则,该集合为所有类型的集合。
2) e 隐式调用了函数(它是运算符表达式且运算符被重载,它是 new-expression 且分配函数被重载,或者它是完整表达式且临时对象的析构函数被调用),则该集合即为该函数的集合。
3) e throw表达式 ,则该集合为其操作数初始化的异常,或(对于无操作数的重新抛出throw表达式)所有类型的集合。
4) e 是通过 dynamic_cast 转换为多态类型的引用,则异常集合包含 std::bad_cast
5) e 是对多态类型解引用指针应用 typeid 的结果,则异常集合包含 std::bad_typeid
6) e 是具有非常量数组大小的 new 表达式 ,且所选分配函数具有非空的潜在异常集合,则该集合包含 std::bad_array_new_length
(C++11 起)
void f() throw(int); // f() 的异常集合为 "int"
void g();            // g() 的异常集合为所有类型的集合
struct A { A(); };                  // "new A" 的异常集合为所有类型的集合
struct B { B() noexcept; };         // "B()" 的异常集合为空
struct D() { D() throw (double); }; // new D 的异常集合为所有类型的集合

所有隐式声明的成员函数 和继承构造函数 (since 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; // 异常规范为 "noexcept(true)"
    B(B&&, int = (throw Y(), 0)) noexcept;
    ~B() throw(Y);
};
int n = 7;
struct D : public A, public B
{
    // 可能抛出与 std​::​bad_array_new_length 类型匹配的异常,但不会抛出内存分配异常
    (void*) new (std::nothrow) int[n];
    // D 可能包含以下隐式声明的成员:
    // 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

关键词

throw

示例

注意:建议在 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++标准。

缺陷报告 应用于 发布时行为 正确行为
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 为空包时也不是非抛出规范 当包为空时为非抛出规范
CWG 2191 C++98 typeid 表达式的潜在异常集合可能包含 bad_typeid ,即使其不可能被抛出 仅当可能被抛出时才包含 bad_typeid

参见

noexcept 说明符 (C++11) 指定函数是否可能抛出异常