声明内联函数noexcept是否有意义?

时间:2014-01-30 17:16:57

标签: c++ c++11 inline noexcept

据我所知,SO社区对于声明函数noexcept是否能够实现有意义的编译器优化存在分歧。 (我正在专门讨论编译器优化,而不是基于move_if_noexcept的库实现优化。)出于这个问题的目的,让我们假设noexcept确实进行了有意义的代码生成优化可能。有了这个假设,声明inline函数noexcept是否有意义?假设这些函数实际内联,这似乎要求编译器在调用站点的try函数的代码周围生成等效的inline块,因为如果该区域出现异常,terminate必须被调用。如果没有noexcept,则try阻止似乎是不必要的。

我最初的兴趣在于声明Lambda函数noexcept是否有意义,因为它们是隐式的inline,但后来我意识到任何inline函数都会出现同样的问题,而不仅仅是Lambdas。

2 个答案:

答案 0 :(得分:11)

  

让我们假设noexcept确实可以进行有意义的代码生成优化

  

假设这些函数实际内联,似乎就是这样   要求编译器生成相当于try块的东西   由调用网站上的内联函数产生的代码,因为   如果该地区出现例外

不一定,因为编译器可能会查看函数体并发现它不可能抛出任何东西。因此,可以省略名义上的异常处理。

如果函数是“完全”内联的(也就是说,如果内联代码不包含函数调用)那么我希望编译器可以相当普遍地做出这个决定 - 但不是例如在有调用的情况下到vector::push_back()并且函数的编写者知道已经保留了足够的空间但是编译器没有。

还要注意,在一个好的实现中,try块实际上可能根本不需要在没有抛出任何内容的情况下执行任何代码。

  

根据这个假设,声明内联函数noexcept是否有意义?

是的,为了得到noexcept的假设优化。

答案 1 :(得分:2)

值得注意的是,关于无关联问题的权力圈内有一个有趣的讨论。我强烈推荐阅读这些:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3227.html

http://www.stroustrup.com/N3202-noexcept.pdf

显然,很有影响力的人都有兴趣为C ++添加某种自动nothrow演绎。

经过一番思考后,我已将我的立场改为几乎相反,见下文

考虑一下:

  • 当您在声明上调用noexcept的函数时 - 您将从中受益(无需处理展开性等)

  • 当编译器在 definion 上编译一个noexcept的函数时 - (除非编译器可以证明该函数确实不存在)性能受损(现在编译器需要确保不异常可以逃避这个功能)。您要求它强制执行无异常承诺

即。 noexcept既伤害了你又伤害了你。如果内联函数则不是这种情况!当它被内联时 - 声明中的noexcept没有任何好处(声明和定义成为一件事)......除非你真的希望编译器为了安全起见强制执行此操作。 safety 我的意思是你宁愿终止而不是产生错误的结果

现在应该很明显 - 没有必要声明内联函数noexcept(请记住,不是每个内联函数都会得到内联)。

让我们看看不抛出的不同类别的函数(你只知道它们没有):

  1. 非内联,编译器可以证明它不会抛出 - noexcept不会损害函数体(编译器会简单地忽略规范)并且调用站点将受益于此承诺

  2. 非内联,编译器无法证明它不会抛出 - noexcept会伤害功能体,但会使呼叫站点受益(很难说什么更有益)

    < / LI>
  3. 内联,编译器可以证明它没有抛出 - noexcept没有用处

  4. 内联,编译器无法证明它不会抛出 - noexcept会伤害调用网站

  5. 如您所见,nothrow只是设计糟糕的语言功能。它仅在您要强制执行无异常承诺时才有效。没有办法正确使用它 - 它可以给你“安全”,但不能提供性能。

    noexcept关键字最终被用作承诺(声明)和强制执行(定义) - 不是一个完美的方法,我认为(大声笑,第二次刺破异常规格,我们仍然没有得到它是对的。)

    那么,该怎么办?

    1. 宣布你的行为(唉,语言没有什么可以帮到你的)! E.g:

      void push_back(int k);   // throws only if there is no unused memory
      
    2. 不要将noexcept放在内联函数上(除非它不可能内联,例如非常大)

    3. 对于非内联函数(或不太可能内联的函数) - 进行调用。较大的函数得到较小的noexcept的负面影响(相对而言) - 在某些时候,为呼叫者的利益指定它可能是有意义的

    4. 在移动构造函数上使用noexcept并移动赋值运算符(和析构函数?)。它可能会对它们产生负面影响,但如果你不这样做 - 某些库函数(std :: swap,某些容器操作)将不会采用最有效的路径(或者不会提供最好的异常保证)。基本上任何在您的函数上使用noexcept 运算符的地方(截至目前)都会强制您使用noexcept 说明符

      < / LI>
    5. 使用noexcept如果你不信任你的函数调用而非死亡,而不是让它出现意外行为

    6. 纯虚函数 - 通常情况下,您不相信实现这些接口的人。购买保险通常是有意义的(通过指定noexcept

    7. 那么,noexcept如何设计?

      1. 我会使用两个不同的关键字 - 一个用于声明一个承诺,另一个用于强制执行。例如。 noexcept force_noexcept 。事实上,执行并不是真正需要的 - 它可以通过try / catch + terminate()来完成(是的,它将展开堆栈,但是谁在乎它是否跟着 std :: terminate()< / EM>?)

      2. 我强制编译器分析给定函数中的所有调用以确定它是否可以抛出。如果确实如此,并且做出了noexcept承诺 - 将发出编译器错误

      3. 对于可以抛出的代码,但是你知道它不应该有一种方法可以确保编译器没问题。像这样的Smth:

        vector<int> v;
        v.reserve(1);
        ...
        nothrow {       // memory pre-allocated, this won't throw
            v.push_back(10);
        }
        

        如果承诺被破坏(即有人更改了矢量代码,现在它提供了其他保证) - 未定义的行为。

      4. 免责声明:这种方法可能太不切实际,谁知道......