命名空间
变体
操作

noexcept 说明符 (C++11 起)

来自 cppreference.cn
< cpp‎ | 语言
 
 
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-specification 不属于函数类型的一部分(就像 动态异常规范 一样),并且只能作为 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-specification 是函数类型的一部分,可以作为任何 函数声明符 的一部分出现。

(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-specification *需要*时,但尚未实例化,则会查找依赖名称,并实例化 表达式 中使用的所有模板,如同用于特化版本的声明一样。

在以下语境中,函数的 noexcept-specification 被认为是*必需的*

  • 在表达式中,函数通过重载决议被选择
  • 该函数被 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 是完整表达式,则为析构函数)
  • e 是一个 throw-表达式
  • 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
};

[编辑] 注意

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

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

[编辑] 废弃

noexceptthrow() 的改进版本,后者在 C++11 中已被废弃。与 C++17 之前的 throw() 不同,noexcept 不会调用 std::unexpected,可能(也可能不)展开栈,并会调用 std::terminate,这可能允许编译器实现 noexcept 而无需 throw() 的运行时开销。从 C++17 开始,throw() 被重新定义为 noexcept(true) 的精确等价物。

功能测试宏 标准 特性
__cpp_noexcept_function_type 201510L (C++17) 使异常规范成为类型系统的一部分

[编辑] 关键词

noexcept, throw(C++17 起)(C++20 前)

[编辑] 示例

// 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
}

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

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

[编辑] 参阅

noexcept 运算符(C++11) 确定表达式是否抛出任何异常[编辑]
动态异常规范(C++17 前) 指定函数抛出哪些异常 (C++11 中废弃) [编辑]
throw 表达式 发出错误信号并将控制转移给错误处理器[编辑]
如果移动构造函数不抛出异常,则将参数转换为亡值
(函数模板) [编辑]