[C++]C++ noexcept 关键字

C++的异常处理

noexcept 说明符可以用于指定某个函数不抛出异常(替代 throw() )
noexcept关键字只会在编译期间影响优化方法,不会对运行期间造成任何影响

设计意图

C++11 为了替代 throw() 而提出的一个新的关键字,在 C++ 中使用函数异常声明列表来查看函数可能抛出的异常,预先知道函数不会抛出异常有助于简化调用该函数的代码,而且编译器确认函数不会抛出异常,它就能执行某些特殊的优化操作。

如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),*程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序*。

**C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。

如何使用

noexcept规范是函数类型的一部分,因此在函数声明和定义中都需要指定。如果你在函数声明中指定了noexcept,那么在函数定义中也必须指定noexcept,反之亦然。如果函数声明和定义中的noexcept规范不一致,那么编译器将会报错。

这与inline关键字有所不同。inline关键字只需要在函数定义中指定,而不需要在函数声明中指定。如果你在函数声明中指定了inline,那么在函数定义中也可以指定inline,但这并不是必需的。

以下是一个例子:

// 声明
void foo() noexcept;

// 定义
void foo() noexcept {
    // ...
}

在这个例子中,函数foo在声明和定义中都指定了noexcept。如果你去掉定义中的noexcept,那么编译器将会报错。

两种异常抛出方式

在实践中,一般两种异常抛出方式是常用的:

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

后面这一种方式中在以往的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);
    }

什么时候该使用noexcept?

使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。

然而,并不是加上noexcept就能提高效率,步子迈大了也容易扯着蛋。 *以下情形鼓励使用noexcept*:

  • 移动构造函数(move constructor)
  • 移动分配函数(move assignment)
  • 析构函数(destructor)因此出于安全考虑,C++11 标准中类的析构函数默认为 noexcept(true)。 但是,如果程序员显式地为析构函数指定了 noexcept(false) 或者类的基类或成员有 noexcept(false) 的析构函数,析构函数就不会再保持默认值。
  • 叶子函数(Leaf Function)叶子函数是指在函数内部不分配栈空间,也不调用其它函数,也不存储非易失性寄存器,也不处理异常。

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


下面代码可以检测编译器是否给析构函数加上关键字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!");
    }

正确使用 noexcept 的注意事项

尽管 noexcept 提供了一种显著提高 C++ 程序性能的方式,但我们需要明白,不是所有的函数都适合声明为 noexcept。下面是一些在使用 noexcept 时应考虑的注意事项:

  • 不要假设 noexcept 函数不会失败。即使函数声明为 noexcept,也可能因为其他原因(如内存分配失败)导致函数失败。此时,如果你的代码没有正确处理这种情况,程序可能会在运行时出现问题。
  • 谨慎使用 noexcept。在没有充分理由的情况下,不要轻易将函数声明为 noexcept。如果一个函数可能抛出任何类型的异常,那么它不应该被声明为 noexcept。
  • 理解 noexcept 的传播规则。在 C++ 中,函数的 noexcept 属性可能会根据其参数和返回类型的 noexcept 属性变化。例如,如果一个函数的返回类型是通过移动构造函数创建的,那么该函数的 noexcept 属性将与移动构造函数的 noexcept 属性相同。
  • 在可能的情况下,优先考虑 noexcept。特别是在设计类时,如果你的成员函数(特别是移动构造函数和移动赋值运算符)能够保证不抛出异常,那么将它们声明为 noexcept 可以提高代码的性能和可读性。

下面是一个示例,说明如何根据条件决定函数是否应声明为 noexcept:

template <class T, class U>
void foo(T& t, U& u) noexcept(noexcept(t.swap(u)))
{
    t.swap(u);
}

在上述代码中,我们声明 foo 函数为 noexcept,但这取决于 T::swap(U&) 的 noexcept 属性。这样做的好处是,如果 T::swap(U&) 是 noexcept 的,那么 foo 函数也会是 noexcept 的。反之,如果 T::swap(U&) 可能抛出异常,那么 foo 函数也可以抛出异常。

使用 noexcept 时要保持谨慎和明智,理解其意图和后果。这将帮助你编写出更安全、更高效的代码。

深入理解 noexcept 及其性能优化

让我们深入研究一下 noexcept 在程序性能优化中的作用。首先,我们需要明确 noexcept 对性能的影响并非直接的,而是通过允许编译器做出某些优化来实现的。

一般来说,编译器在处理可能抛出异常的函数时需要考虑的情况更多,因此需要生成更复杂的代码,尤其是在涉及栈展开(stack unwinding)的情况下。当一个函数标记为 noexcept 时,编译器可以确保这个函数不会抛出异常,从而在生成代码时可以忽略处理异常的部分,产生更简洁、更高效的代码。

最直接的影响是编译器可能不需要生成处理异常的代码,也不需要在函数调用之后检查是否有异常被抛出。这可以减少生成的代码的大小,也可能使代码运行得更快。然而,这种优化通常是微不足道的,因为大多数函数调用的成本都远大于检查异常的成本。 另一个可能的优化是,如果编译器知道一个函数不会抛出异常,它可能更愿意将这个函数内联。这是因为异常处理代码通常不能被内联,所以如果一个函数可能抛出异常,编译器可能会选择不将其内联。然而,这种优化也是依赖于具体的编译器和优化级别的。

此外,noexcept 还可以影响 C++ 对象的移动语义。特别是在容器重排序或调整大小等操作时,如果一个对象的移动构造函数和移动赋值运算符被标记为 noexcept,那么 C++ 运行时环境可以安全地移动这些对象,而不是进行更复杂、更耗费时间的拷贝操作。

让我们看一下一个简单的例子,说明 noexcept 如何提升性能:

void process_elements(std::vector<MyType>& elements) noexcept
{
    for(auto& elem : elements)
    {
        // Some complex processing on elem...
    }
    // Rearrange elements for next processing phase.
    std::sort(elements.begin(), elements.end());
}

在上面的例子中,如果 MyType 的移动构造函数和移动赋值运算符都是 noexceptstd::sort 可以用更有效的方法来移动元素,从而提升整体性能。

因此,noexcept 不仅表示函数的异常安全性,还可以对函数的性能产生重要影响。

重要提醒: 虽然 noexcept 可以提高性能,但我们不应滥用它。只有当你确定一个函数不会抛出异常时,才应将其声明为 noexcept。记住,标记为 noexcept 的函数如果抛出了异常,程序将会直接调用 std::terminate() 终止运行,这通常是我们想要避免的。

  • 29
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FL1768317420

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值