c++ noexcept

https://www.cnblogs.com/sword03/p/10020344.html

 

C++11 带来的新特性 (3)—— 关键字noexcept

1 关键字noexcept

从C++11开始,我们能看到很多代码当中都有关键字noexcept。比如下面就是std::initializer_list的默认构造函数,其中使用了noexcept。

      constexpr initializer_list() noexcept
      : _M_array(0), _M_len(0) { }

该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。
如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。

2 C++的异常处理

C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。
在实践中,一般两种异常抛出方式是常用的:

  • 一个操作或者函数可能会抛出一个异常;
  • 一个操作或者函数不可能抛出任何异常。

后面这一种方式中在以往的C++版本中常用throw()表示,在C++ 11中已经被noexcept代替。

    void swap(Type& x, Type& y) throw()   //C++11之前
    {
        x.swap(y);
    }
    void swap(Type& x, Type& y) noexcept  //C++11
    {
        x.swap(y);
    }

3 有条件的noexcecpt

在第2节中单独使用noexcept,表示其所限定的swap函数绝对不发生异常。然而,使用方式可以更加灵活,表明在一定条件下不发生异常。

    void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y)))    //C++11
    {
        x.swap(y);
    }

它表示,如果操作x.swap(y)不发生异常,那么函数swap(Type& x, Type& y)一定不发生异常。

一个更好的示例是std::pair中的移动分配函数(move assignment),它表明,如果类型T1和T2的移动分配(move assign)过程中不发生异常,那么该移动构造函数就不会发生异常。

    pair& operator=(pair&& __p)
    noexcept(__and_<is_nothrow_move_assignable<_T1>,
                    is_nothrow_move_assignable<_T2>>::value)
    {
        first = std::forward<first_type>(__p.first);
        second = std::forward<second_type>(__p.second);
        return *this;
    }

4 什么时候该使用noexcept?

使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。然而,并不是加上noexcept就能提高效率,步子迈大了也容易扯着蛋。
以下情形鼓励使用noexcept:

  • 移动构造函数(move constructor)
  • 移动分配函数(move assignment)
  • 析构函数(destructor)。这里提一句,在新版本的编译器中,析构函数是默认加上关键字noexcept的。下面代码可以检测编译器是否给析构函数加上关键字noexcept。
    struct X
    {
        ~X() { };
    };
    
    int main()
    {
        X x;
    
        // This will not fire even in GCC 4.7.2 if the destructor is
        // explicitly marked as noexcept(true)
        static_assert(noexcept(x.~X()), "Ouch!");
    }
  • 叶子函数(Leaf Function)。叶子函数是指在函数内部不分配栈空间,也不调用其它函数,也不存储非易失性寄存器,也不处理异常。

最后强调一句,在不是以上情况或者没把握的情况下,不要轻易使用noexception。

<think>我们正在讨论C++中的noexcept关键字。用户已经提供了三个引用,这些引用解释了noexcept的作用和应用场景。用户明确要求了解noexcept的用法和含义,因此我们需要详细解释noexcept的使用方法、作用以及最佳实践。根据引用,我们可以总结以下几点:1.noexceptC++11引入的关键字,用于指示函数是否可能抛出异常。2.它可以用在函数声明或定义中,表示该函数不会抛出异常,或者程序员确保它不会抛出异常。3.在系统设计中合理使用noexcept可以提高系统的健壮性和可预测性,特别是在资源管理(如析构函数)和标准库函数(如swap)中尤为重要。4.引用[2]中的示例展示了noexcept函数和普通函数在异常处理上的区别:当noexcept函数抛出异常时,程序会直接终止(调用std::terminate),而不会捕获到异常。因此,我们的回答应该包括以下内容:1.noexcept的语法形式。2.noexcept的作用(包括性能优化和接口契约)。3.何时使用noexcept(例如析构函数、移动操作、swap等)。4.noexcept在标准库中的应用和交互。5.示例代码说明noexcept的使用和效果。6.与异常说明符throw()的对比(C++11之前)。7.条件性noexceptnoexcept运算符和表达式)。8.注意事项(例如,如果noexcept函数抛出了异常会怎样)。另外,根据用户提供的引用,我们还需要注意:-引用[1]强调了noexcept在系统健壮性和可预测性上的作用,以及与标准库的交互。-引用[2]提供了一个示例,说明noexcept函数和普通函数在异常处理上的区别。-引用[3]简单定义了noexcept的作用。我们还需要参考用户之前的问题(关于typedef)和我们的回答,但当前问题与typedef无关,所以主要关注noexcept。最后,按照系统级指令:-数学表达式使用$...$格式,但本问题可能不涉及数学表达式。-独立公式使用$$...$$格式,同样可能不涉及。-使用中文回答。-生成相关问题。-引用的段落末尾自然地添加引用标识。因此,我们组织回答如下:###一、noexcept的基本含义noexceptC++11引入的关键字,用于指定函数是否可能抛出异常。它有两种形式:1.**无条件形式**:`noexcept`或`noexcept(true)`,表示函数不会抛出任何异常。2.**条件形式**:`noexcept(expression)`,其中`expression`是一个常量表达式,结果为`true`时表示不抛出异常,`false`时可能抛出。###二、noexcept的作用1.**优化机会**:编译器知道函数不会抛出异常时,可省略异常处理机制的开销(如栈展开代码),生成更高效的代码[^2]。2.**接口契约**:明确告知调用者该函数不会抛出异常,调用者无需编写异常处理代码。3.**标准库要求**:标准库中的某些操作(如`std::swap`、移动操作)要求或建议使用`noexcept`以提供强异常安全保证[^1]。4.**程序终止安全**:若`noexcept`函数意外抛出异常,程序会立即调用`std::terminate()`终止运行,避免异常传播导致未定义行为[^2][^3]。###三、使用方法与示例####1.基本声明```cppvoidsafe_function()noexcept;//无条件不抛出异常voidmaybe_throws()noexcept(false);//可能抛出异常(默认行为,可省略)```####2.条件性noexcept```cpptemplate<typenameT>voidswap(T&a,T&b)noexcept(noexcept(T::swap(a,b))){T::swap(a,b);//当T::swap不抛出异常时,本函数也不抛出}```此处内层`noexcept`是运算符,用于检查表达式`T::swap(a,b)`是否可能抛出异常。####3.实际效果示例```cpp#include<iostream>#include<stdexcept>voidthrows_exception(){throwstd::runtime_error("error");}voidno_exception()noexcept{throws_exception();//此处调用抛出异常的函数!}intmain(){try{no_exception();//noexcept函数内部抛出异常}catch(...){std::cout<<"Caughtexception!"<<std::endl;//不会执行}return0;}```运行结果:**程序直接终止**(调用`std::terminate()`),因为`no_exception`承诺不抛出异常,但实际抛出了[^2][^3]。###四、应用场景1.**析构函数**:C++标准要求析构函数默认不抛出异常,因此编译器通常隐式为析构函数添加`noexcept`[^1]。若自定义析构函数可能抛出,需显式标记`noexcept(false)`(但强烈不推荐)。2.**移动构造函数/赋值运算符**:标准库容器(如`std::vector`)在扩容时,若元素类型的移动操作标记为`noexcept`,则优先使用移动而非拷贝,提升性能[^1]。```cppclassMyClass{public:MyClass(MyClass&&other)noexcept{...}//移动构造MyClass&operator=(MyClass&&other)noexcept{...}//移动赋值};```3.**交换函数`swap`**:标准算法(如`std::sort`)依赖`noexceptswap`实现强异常安全。```cppvoidswap(MyClass&a,MyClass&b)noexcept{...}```###五、与`throw()`的对比C++11前用`throw()`声明不抛出异常,但若函数抛出异常会调用`std::unexpected()`(非直接终止),且语法笨重。C++11中`noexcept`更高效简洁:```cpp//旧风格voidold_func()throw();//动态检查,开销大//新风格voidnew_func()noexcept;//静态优化,直接终止```###六、注意事项1.**谨慎添加`noexcept`**:错误标记可能导致程序意外终止。仅在确定函数及所有调用链均不抛异常时使用。2.**虚函数重写**:派生类重写的虚函数必须与基类的异常说明一致(即基类若`noexcept`,派生类也必须`noexcept`)。3.**模板泛型**:条件性`noexcept`常用于模板,根据类型操作是否`noexcept`动态决定。>核心原则:**仅对真正不抛异常的函数使用`noexcept`,它既是优化开关也是质量承诺。**---###相关问题1.如何检测一个函数是否声明为`noexcept`?2.`noexcept`在移动语义中如何影响容器性能?3.为什么析构函数默认是`noexcept`?4.如何为可能抛出异常的函数正确使用异常说明?[^1]:[C++关键字noexcept应用及案例](引用1的URL)[^2]:[c++11中noexcept关键字作用详解](引用2的URL)[^3]:[C++noexcept是干什么的](引用3的URL)</think>### C++ `noexcept` 关键字详解 `noexcept` 是 C++11 引入的关键字,用于显式声明函数**不会抛出异常**,既是编译器优化提示,也是接口契约的保证。以下是核心用法和作用: --- #### 一、基本语法与作用 ```cpp void func() noexcept; // 无条件声明不抛异常 void func() noexcept(true); // 等价于 noexcept void func() noexcept(false); // 可能抛异常(默认行为) ``` - **核心作用**: 1. **优化代码生成**:编译器移除异常处理开销,生成更高效的代码[^2] 2. **接口契约**:明确告知调用者无需异常处理 3. **标准库交互**:影响容器操作(如 `vector` 扩容时的元素移动策略)[^1] 4. **安全终止**:若 `noexcept` 函数意外抛异常,直接调用 `std::terminate()` 终止程序[^3] --- #### 二、关键应用场景 1. **移动构造函数/赋值运算符** 标记为 `noexcept` 使标准容器优先使用移动而非拷贝: ```cpp class MyType { public: MyType(MyType&& other) noexcept { ... } // 移动构造 MyType& operator=(MyType&& other) noexcept { ... } // 移动赋值 }; std::vector<MyType> v; v.push_back(MyType{}); // 若移动操作noexcept,则使用移动优化 ``` 2. **析构函数** 析构函数默认隐式 `noexcept`,若可能抛异常需显式标记 `noexcept(false)`: ```cpp ~ResourceHolder() noexcept(false) { if (cleanup_failed) throw std::runtime_error("Cleanup error"); } ``` 3. **交换函数 `swap`** 标准库算法依赖 `noexcept swap` 实现强异常安全: ```cpp void swap(MyType& a, MyType& b) noexcept { using std::swap; swap(a.data, b.data); // 无异常操作 } ``` 4. **高性能关键路径** 确保关键函数无异常开销: ```cpp void process_realtime_data() noexcept { // 实时数据处理(禁止异常) } ``` --- #### 三、条件性 `noexcept`(高级用法) 使用 `noexcept(表达式)` 根据类型特性动态决定: ```cpp template<typename T> void safe_swap(T& a, T& b) noexcept(noexcept(T::swap(a, b))) { T::swap(a, b); // 仅当T::swap不抛异常时本函数noexcept } ``` - **嵌套 `noexcept`**:内层 `noexcept` 是运算符,返回 `bool` 表示表达式是否不抛异常 --- #### 四、与 `throw()` 的对比 | 特性 | `noexcept` | `throw()` (C++11前) | |---------------|--------------------------------|-----------------------------| | **终止行为** | 直接调用 `std::terminate()` | 先调用 `std::unexpected()` | | **性能开销** | 零运行时开销 ✅ | 有运行时检查 ❌ | | **语法简洁性**| 简洁(`noexcept`)✅ | 需完整异常列表 ❌ | ```cpp // 现代C++优先使用noexcept void legacy_func() throw(); // 已废弃 void modern_func() noexcept; // 推荐 ``` --- #### 五、使用注意事项 1. **契约可靠性** 错误标记 `noexcept` 的函数若抛出异常,程序立即终止。仅对**真正无异常**的函数使用。 ```cpp void risky_op() noexcept { throw std::logic_error("oops"); // 程序直接终止! } ``` 2. **虚函数重写一致性** 派生类虚函数的异常说明必须与基类兼容: ```cpp struct Base { virtual void foo() noexcept; }; struct Derived : Base { void foo() noexcept override; // 必须同为noexcept }; ``` 3. **模板元编程** 结合 `std::is_nothrow_constructible` 等类型特征: ```cpp template<typename T> void construct(T* p) noexcept(std::is_nothrow_constructible_v<T>) { new (p) T(); // 根据T的构造特性决定是否noexcept } ``` --- #### 六、最佳实践 1. **移动操作默认标记 `noexcept`** 提升容器操作效率,符合标准库预期[^1] 2. **析构函数避免抛异常** 若非必要不标记 `noexcept(false)`,防止资源泄漏 3. **高性能组件强制 `noexcept`** 如实时系统、内存分配器等关键路径 4. **优先使用无条件 `noexcept`** 条件形式仅用于泛型编程场景 > 核心原则:**`noexcept` 是性能优化与安全契约的工具,而非异常处理的替代品。** --- ### 相关问题 1. 如何检测一个函数是否声明为 `noexcept`? 2. `noexcept` 在移动语义中如何影响 `std::vector` 的性能? 3. 为什么析构函数默认是 `noexcept`? 4. 如何为可能抛出异常的函数正确设计异常安全接口? [^1]: C++关键字noexcept应用及案例 [^2]: c++11中noexcept关键字作用详解 [^3]: C++ noexcept 是干什么的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值