命名空间
变体
操作

动态异常规范 (至 C++17)

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

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

内容

[编辑] 语法

throw(类型标识符列表 (可选)) (1) (在 C++11 中已弃用)
(在 C++17 中已删除)
1) 显式动态异常规范。
类型标识符列表 - 用逗号分隔的 类型标识符,表示 包展开 的类型标识符后跟省略号 (...)(自 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 都有一组*潜在异常*,它包含可能抛出的类型。所有类型的集合表示可能会抛出任何异常。该集合定义如下:

1) 如果 fpfpmf 的声明使用动态异常规范不允许所有异常(直到 C++11),则该集合包含该规范中列出的类型。
2) 否则,如果 fpfpmf 的声明使用 noexcept(true),则该集合为空。
(自 C++11 起)
3) 否则,该集合是所有类型的集合。

注意:对于隐式声明的特殊成员函数(构造函数、赋值运算符和析构函数)以及继承的构造函数(自 C++11 起),潜在异常的集合是它们将调用的所有内容的潜在异常集合的组合:非变体非静态数据成员、直接基类以及在适当情况下,虚拟基类的构造函数/赋值运算符/析构函数(包括默认参数表达式,一如既往)。

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

1) 如果 e 是一个函数调用表达式,令 g 表示被调用的函数、函数指针或指向成员函数的指针,那么
  • 如果 g 的声明使用动态异常规范,则将 g 的潜在异常集合添加到该集合中;
(自 C++11 起)
  • 否则,该集合是所有类型的集合。
2) 如果 e 隐式调用函数(它是一个运算符表达式,并且运算符被重载;它是一个 new 表达式,并且分配函数被重载;或者它是一个完整表达式,并且临时对象的析构函数被调用),则该集合是该函数的集合。
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()'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

[编辑] 关键字

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

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 是一个空包
它是非抛出的
如果包为空
CWG 2191 C++98 typeid 表达式的潜在异常集合
可能包含 bad_typeid,即使它不能被抛出
包含 bad_typeid
仅当它可以被抛出时

[编辑] 参见

noexcept 说明符(C++11) 指定函数是否可以抛出异常[编辑]