美文网首页
深入使用noexcept

深入使用noexcept

作者: 疾风2018 | 来源:发表于2023-01-15 20:02 被阅读0次

    深入使用noexcept

    简介

    noexcept是C++11引入的,表明函数是否会抛出异常。正确使用它可以优化性能。

    noexcept使用语法有两种:

    1. noexcpet
    2. noexcept(expression)

    第二种使用方式允许用表达式决定是否noexcept起效果,当expression的值为true的时候起效果,否则不起效。expression是编译时求值,一切都是在编译时决定。

    好处

    如果一个函数标注成noexcept

    • 可以选择移动构造函数 / 移动赋值运算符;
    • 编译器就不用生成异常处理代码了,因此可以优化编译。

    坏处

    如果noexcept的函数执行时出了异常,包括所调用的函数抛出的异常,程序会马上terminate,即使套上try...catch也仍旧会terminate。并且编译器不会帮你检查这样的风险。

    适用场景

    在需要决定是调用移动构造函数(或者移动赋值运算符)还是复制构造函数(或者复制赋值运算符)时,noexcept会影响决定。因为移动语法会“破坏”原来的源对象的内容,造成无法在出现异常情况下恢复状态。因此只有移动构造函数(或者移动赋值运算符)标明为noexcept时,才能在这种情况下使用移动构造函数(或者移动赋值运算符)替代复制构造函数(或者复制赋值运算符)。

    一个例子就是STL库中的vector的扩容,扩容涉及到是复制对象还是移动对象的问题,就是上述的问题。当对象的移动构造函数可能会抛出异常的时候,vector是”不敢“在这个场景下调用的,因为出了异常无法原恢复状态。

    详细逻辑如下:

    1. 当使用复制构造时,抛出异常时只要把已复制的对象销毁,新分配的内存释放,一切还能恢复到跟以前一样;
    2. 当使用移动构造时,容器中的原来的元素的状态已经被移动构造破坏了,无法恢复到跟以前一样。

    以下是测验代码:

    class A {
    public:
        A() { std::cout << "constructor" << std::endl; }
        A(const A& a) { std::cout << "copy constructor" << std::endl; }
        A(const A&& a) noexcept { std::cout << "move constructor" << std::endl; } // 有noconcept时,扩容时用移动构造
        // A(const A&& a) { std::cout << "move constructor" << std::endl; }  // 去掉noconcept时,扩容时用拷贝构造
    };
    
    int main() {
        std::vector<A> v;
        v.reserve(1);
        for (int i = 0; i < 10; i++) {
            A a; // 构造一个A类的实例。
            v.push_back(a); // 添加进容器时会调用一次复制构造,如果容量不够则会扩容,这时候会选择复制构造还是移动构造。
        }
    
        return 0;
    }
    

    不适用场景

    其他情况均不太适合使用。因为:

    1. 编译器不会帮你做检查,假如一个标注noexcept的函数调用未标注noexcept的函数,是可以顺利编译的。但是未标注nonexcept的函数是不保证不抛异常的。
    2. 一个函数加上noexcept之后就可能很难移除,因为其他代码可能会直接或间接引用到它,且假设不会有异常;
    3. 如果一个标注了noexcept的函数自己或者调用的函数(直接或间接)抛出异常,直接终止,非常简单粗暴。

    因此出了几个有限的适用场景外,其他情况下不要用noexcept

    实验结果

    以下图表是来自于 C++ noexcept and move constructors effect on performance in STL Containers — TRYING TO FIND THE OBVIOUS (hlsl.co.uk) 这篇博客的实验结果。

    实验结果

    根据实验结果,性能提升了两倍!

    总结

    虽然noexcept会在某些情况下提升性能,但是由于它的危险性,包括发生异常直接终止程序且编译器不会帮你检查,除了以下情况下都不建议使用。

    1. 移动构造函数
    2. 移动赋值运算符
    3. 析构函数
    4. 简单函数

    1、2 已经在前面说过了,不再赘述。对于3、4,析构函数本身不应该抛异常,简单函数一般不会发生异常,因此可以放心标注noexcept

    参考资料

    1. c++ 从vector扩容看noexcept应用场景 - 知乎 (zhihu.com)

    2. C++ noexcept and move constructors effect on performance in STL Containers — TRYING TO FIND THE OBVIOUS (hlsl.co.uk)

    相关文章

      网友评论

          本文标题:深入使用noexcept

          本文链接:https://www.haomeiwen.com/subject/sxvacdtx.html