命名空间
变体
操作

noexcept 说明符 (自 C++11 起)

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

指定函数是否可以抛出异常。

内容

[编辑] 语法

noexcept (1)
noexcept(表达式) (2)
throw() (3) (C++17 中已弃用)
(C++20 中已删除)
1)noexcept(true) 相同
2) 如果 表达式 计算结果为 true,则该函数被声明为不抛出任何异常。( 紧跟在 noexcept 后面始终是此形式的一部分(它永远不会开始初始化器)。
3)noexcept(true) 相同(有关 C++17 之前语义,请参阅 动态异常规范
表达式 - 上下文转换的常量表达式,类型为 bool

[编辑] 说明

noexcept 规范不是函数类型的一部分(就像 动态异常规范 一样),并且只能在声明函数、变量、类型为函数的非静态数据成员、指向函数的指针、指向函数的引用或指向成员函数的指针时作为 lambda 说明符 或顶级 函数说明符 的一部分出现,以及在声明这些声明中的参数或返回类型时出现,而这些声明又恰好是指向函数的指针或引用。它不能出现在 typedef类型别名 声明中。

void f() noexcept; // the function f() does not throw
void (*fp)() noexcept(false); // fp points to a function that may throw
void g(void pfa() noexcept);  // g takes a pointer to function that doesn't throw
// typedef int (*pf)() noexcept; // error
(直到 C++17)

noexcept 规范是函数类型的一部分,可以作为任何 函数说明符 的一部分出现。

(自 C++17 起)

C++ 中的每个函数都是非抛出潜在抛出

  • 潜在抛出函数是
(直到 C++17)
  • 使用noexcept说明符声明的函数,其表达式计算结果为false
  • 除了以下情况外,没有使用noexcept说明符声明的函数
  • 基类或成员的构造函数(构造函数的隐式定义将调用该构造函数)是可能抛出异常的(见下文)
  • 此类初始化的子表达式(例如默认参数表达式)是可能抛出异常的(见下文)
  • 默认成员初始化器(仅针对默认构造函数)是可能抛出异常的(见下文)
  • 复制赋值运算符,移动赋值运算符,这些运算符是在其第一次声明时隐式声明或默认为,除非隐式定义中任何赋值运算符的调用是可能抛出异常的(见下文)
  • 比较运算符,这些运算符是在其第一次声明时默认为,除非隐式定义中任何比较运算符的调用是可能抛出异常的(见下文)
(自 C++20 起)
  • 非抛出异常函数是所有其他函数(具有noexcept说明符且其表达式计算结果为true的函数,以及析构函数、默认的特殊成员函数和释放函数)

显式实例化可以使用noexcept说明符,但并非必需。如果使用,异常说明必须与所有其他声明相同。只有当异常说明在单个翻译单元内不一致时,才需要诊断。

只有异常说明不同的函数无法重载(就像返回值类型一样,异常说明是函数类型的一部分,但不是函数签名的一部分)(自 C++17 起).

void f() noexcept;
void f(); // error: different exception specification
void g() noexcept(false);
void g(); // ok, both declarations for g are potentially-throwing

指向非抛出异常函数的指针(包括指向成员函数的指针)可以分配给或用于初始化(直到 C++17)可以隐式转换为(自 C++17 起)指向可能抛出异常函数的指针,反之则不行。

void ft(); // potentially-throwing
void (*fn)() noexcept = ft; // error

如果虚函数是非抛出异常的,则所有声明(包括定义)中的每个覆盖函数也必须是非抛出异常的,除非覆盖函数被定义为已删除

struct B
{
    virtual void f() noexcept;
    virtual void g();
    virtual void h() noexcept = delete;
};
 
struct D: B
{
    void f();          // ill-formed: D::f is potentially-throwing, B::f is non-throwing
    void g() noexcept; // OK
    void h() = delete; // OK
};

非抛出异常函数允许调用可能抛出异常函数。当抛出异常并且处理程序搜索遇到非抛出异常函数的最外层块时,该函数将调用std::terminate

extern void f(); // potentially-throwing
 
void g() noexcept
{
    f();      // valid, even if f throws
    throw 42; // valid, effectively a call to std::terminate
}

函数模板特化的异常说明不会与函数声明一起实例化;它只有在需要时才会实例化(如下定义)。

隐式声明的特殊成员函数的异常说明也只有在需要时才会计算(特别是,派生类成员函数的隐式声明不需要实例化基类成员函数的异常说明)。

当函数模板特化的noexcept说明需要但尚未实例化时,将查找依赖名称,并将noexcept说明中使用的任何模板实例化为好像用于该特化的声明一样。

在以下情况下,函数的noexcept说明被认为是需要

  • 在一个表达式中,其中该函数通过重载解析被选中
  • 该函数被odr-使用
  • 该函数将被odr-使用,但出现在未评估的操作数中
template<class T>
T f() noexcept(sizeof(T) < 4);
 
int main()
{
    decltype(f<void>()) *p; // f unevaluated, but noexcept-spec is needed
                            // error because instantiation of the noexcept specification 
                            // calculates sizeof(void)
}
  • 需要该说明来与另一个函数声明进行比较(例如,在虚函数覆盖或函数模板的显式特化上)
  • 在函数定义中
  • 需要该说明,因为默认的特殊成员函数需要检查它才能决定自己的异常说明(这只有在默认的特殊成员函数的说明本身需要时才会发生)。

可能抛出异常的表达式的正式定义(用于确定析构函数、构造函数和赋值运算符的默认异常说明,如上所述)

如果表达式e满足以下条件,则它是可能抛出异常的

  • e是对可能抛出异常的函数、指向函数的指针或指向成员函数的指针的函数调用,除非e核心常量表达式(直到 C++17)
  • e可能抛出异常的函数进行隐式调用(例如,重载运算符、new表达式中的分配函数、函数参数的构造函数或析构函数(如果e是完整表达式))
  • ethrow表达式
  • e是将多态引用类型转换为其他类型的dynamic_cast
  • e是应用于对多态类型解除引用指针的typeid表达式
  • e有一个可能抛出异常的直接子表达式
struct A
{
    A(int = (A(5), 0)) noexcept;
    A(const A&) noexcept;
    A(A&&) noexcept;
    ~A();
};
 
struct B
{
    B() throw();
    B(const B&) = default; // implicit exception specification is noexcept(true)
    B(B&&, int = (throw Y(), 0)) noexcept;
    ~B() noexcept(false);
};
 
int n = 7;
struct D : public A, public B
{
    int * p = new int[n];
    // D::D() potentially-throwing because of the new operator
    // D::D(const D&) non-throwing
    // D::D(D&&) potentially-throwing: the default argument for B’s constructor may throw
    // D::~D() potentially-throwing
 
    // note; if A::~A() were virtual, this program would be ill-formed because an overrider
    // of a non-throwing virtual cannot be potentially-throwing
};

[edit] Notes

常量表达式的用途之一是(与noexcept运算符一起)定义对某些类型声明为noexcept,而对其他类型则不声明为noexcept的函数模板。

请注意,函数上的noexcept说明不是编译时检查;它仅仅是一种方法,供程序员告知编译器函数是否应该抛出异常。编译器可以使用此信息对非抛出异常函数启用某些优化,并启用noexcept运算符,该运算符可以在编译时检查特定表达式是否声明为抛出任何异常。例如,像std::vector这样的容器将在元素的移动构造函数为noexcept的情况下移动其元素,否则将复制(除非复制构造函数不可访问,但存在可能抛出异常的移动构造函数,在这种情况下,强异常保证将被放弃)。

[edit] Deprecates

noexceptthrow()的改进版本,后者在 C++11 中已被弃用。与 C++17 之前的throw()不同,noexcept不会调用std::unexpected,可能或可能不反转堆栈,并将调用std::terminate,这可能允许编译器实现noexcept,而无需throw()的运行时开销。自 C++17 起,throw()被重新定义为与noexcept(true)完全相同。

特性测试宏 Std 特性
__cpp_noexcept_function_type 201510L (C++17) 使异常说明成为类型系统的一部分

[edit] Keywords

noexcept, throw(自 C++17 起)(直到 C++20)

[edit] Example

// whether foo is declared noexcept depends on if the expression
// T() will throw any exceptions
template<class T>
void foo() noexcept(noexcept(T())) {}
 
void bar() noexcept(true) {}
void baz() noexcept { throw 42; } // noexcept is the same as noexcept(true)
 
int main() 
{
    foo<int>(); // noexcept(noexcept(int())) => noexcept(true), so this is fine
 
    bar(); // fine
    baz(); // compiles, but at runtime this calls std::terminate
}

[edit] Defect reports

以下行为更改缺陷报告被追溯应用于之前发布的 C++ 标准。

DR 应用于 发布的行为 正确行为
CWG 1330 C++11 异常说明可能被急切实例化 它只有在需要时才会实例化
CWG 1740 C++11 ( 紧跟在 noexcept 之后可能开始一个初始化器 它只能是
noexcept 说明的一部分
CWG 2039 C++11 只有转换之前的表达式需要是常量 转换也必须是
常量表达式中有效的

[edit] See also

noexcept 运算符(C++11) 确定表达式是否抛出任何异常[edit]
动态异常说明(直到 C++17) 指定函数抛出的异常(C++11 中已弃用) [edit]
throw 表达式 发出错误信号并将控制权转移到错误处理程序[edit]
如果移动构造函数不抛出异常,则将参数转换为右值引用
(函数模板) [edit]