Namespaces
Variants

noexcept specifier (since C++11)

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)

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

目录

语法

noexcept (1)
noexcept( expression ) (2)
throw() (3) (C++17 中弃用)
(C++20 中移除)
1) 等同于 noexcept(true)
2) expression 求值为 true ,则声明该函数不抛出任何异常。 noexcept 后接的 ( 始终属于此形式(绝不能作为初始化器的起始符号)。
3) noexcept(true) 相同(有关其在 C++17 之前的语义,请参阅 动态异常规范
expression - 上下文转换的 bool 类型常量表达式

说明

noexcept 说明符不是函数类型的一部分(如同 动态异常说明 ),并且只能在声明函数、函数指针类型、函数引用类型、指向成员函数的指针类型的变量或非静态数据成员时,作为 lambda 声明符 或顶层 函数声明符 的一部分出现。它也可以在那些声明中作为参数或返回类型出现,当这些参数或返回类型本身恰好是函数指针或函数引用时。它不能出现在 typedef 类型别名 声明中。

void f() noexcept; // 函数 f() 不抛出异常
void (*fp)() noexcept(false); // fp 指向可能抛出异常的函数
void g(void pfa() noexcept);  // g 接受指向不抛出异常的函数的指针
// typedef int (*pf)() noexcept; // 错误
(C++17 前)

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

(C++17 起)

C++中的每个函数要么是 非抛出 的,要么是 可能抛出 的:

  • potentially-throwing 函数包括:
(直至 C++17)
  • 使用 noexcept 说明符声明且其 表达式 求值为 false 的函数
  • 未使用 noexcept 说明符声明的函数,但以下情况除外:
  • 该构造函数的隐式定义会调用的基类或成员构造函数是 潜在抛出 的(详见下文)
  • 此类初始化中的子表达式(如默认实参表达式)是 潜在抛出 的(详见下文)
  • 默认成员初始化器(仅针对默认构造函数)是 潜在抛出 的(详见下文)
  • 隐式声明或首次声明时缺省定义的 复制赋值 运算符、 移动赋值 运算符,除非隐式定义中调用的任何赋值运算符是 潜在抛出 的(详见下文)
  • 在首次声明时被默认化的 比较运算符 ,除非隐式定义中任何比较运算符的调用是 可能抛出异常的 (见下文)
(C++20 起)
  • 非抛出函数包括所有其他函数(具有 noexcept 说明符且其 表达式 求值为 true 的函数,以及析构函数、默认化的特殊成员函数和释放函数)

显式实例化 可以使用 noexcept 说明符,但这不是必需的。如果使用,异常规范必须与所有其他声明保持一致。仅当在同一翻译单元内的异常规范不一致时才需要产生诊断信息。

仅在异常规范上不同的函数不能重载 (与返回类型类似,异常规范是函数类型的一部分,但不是函数签名的一部分) (since C++17)

void f() noexcept;
void f(); // 错误:异常规范不同
void g() noexcept(false);
void g(); // 正确,两个g的声明都是可能抛出的

指向不抛出异常的函数的指针(包括指向成员函数的指针) 可以被赋值给或用于初始化 (直至 C++17) 隐式转换 (自 C++17 起) 指向可能抛出异常的函数的指针,但反之则不成立。

void ft(); // 可能抛出异常
void (*fn)() noexcept = ft; // 错误

如果虚函数不抛出异常,那么每个重写器的所有声明(包括定义)都必须同样不抛出异常,除非该重写器被定义为已删除:

struct B
{
    virtual void f() noexcept;
    virtual void g();
    virtual void h() noexcept = delete;
};
struct D: B
{
    void f();          // 非良构:D::f 可能抛出异常,而 B::f 为不抛出异常
    void g() noexcept; // 正确
    void h() = delete; // 正确
};

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

extern void f(); // 可能抛出异常
void g() noexcept
{
    f();      // 有效,即使f抛出异常
    throw 42; // 有效,实际上会调用std::terminate
}

函数模板特化的异常规范不会随函数声明一同实例化;仅在 需要时 (按下文定义)才会进行实例化。

隐式声明的特殊成员函数的异常规范同样仅在需要时被求值(特别是,派生类成员函数的隐式声明不需要实例化基类成员函数的异常规范)。

当需要函数模板特化的 noexcept 规范但尚未实例化时,依赖名称会被查找,且表达式中使用的任何模板都会像为特化声明那样进行实例化。

在以下情境中,函数的 noexcept 规范被视为 需要 的:

  • 在表达式中,通过重载决议选择函数时
  • 函数被 ODR使用
  • 函数本应被ODR使用但出现在未求值操作数中
template<class T>
T f() noexcept(sizeof(T) < 4);
int main()
{
    decltype(f<void>()) *p; // f 未求值,但需要 noexcept 规范
                            // 错误,因为实例化 noexcept 规范
                            // 会计算 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; // 隐式异常规范为 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() 可能抛出异常(由于 new 运算符)
    // D::D(const D&) 不抛出异常
    // D::D(D&&) 可能抛出异常:B 构造函数的默认参数可能抛出异常
    // D::~D() 可能抛出异常
    // 注意:如果 A::~A() 是虚函数,此程序将非法,因为非抛出虚函数的重写函数不能可能抛出异常
};

注释

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

请注意,函数上的 noexcept 规范并非编译时检查;它仅仅是程序员向编译器声明函数是否应抛出异常的一种方式。编译器可利用此信息对不抛出异常的函数启用特定优化,同时启用 noexcept 运算符 ,该运算符可在编译时检查特定表达式是否被声明为会抛出异常。例如,像 std::vector 这样的容器会在元素移动构造函数被声明为 noexcept 时移动元素,否则将进行复制操作(除非复制构造函数不可访问但可能抛出异常的移动构造函数可用,此时将放弃强异常保证)。

已弃用

noexcept throw ( ) 的改进版本,后者在 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 后可能开始初始化器 只能作为异常规范的一部分
CWG 2039 C++11 仅要求转换前的表达式为常量 转换也必须在常量表达式中有效

参见

noexcept operator (C++11) 判断表达式是否抛出任何异常
Dynamic exception specification (until C++17) 指定函数抛出的异常类型 (C++11 中弃用)
throw expression 发出错误信号并将控制权转移给错误处理程序
如果移动构造函数不抛出异常,则将参数转换为 xvalue
(函数模板)