19

据我所知,SO 社区在声明一个函数是否noexcept能够实现有意义的编译器优化方面存在分歧,而这在其他情况下是不可能的。(我说的是编译器优化,而不是基于move_if_noexcept. 有了这个假设,声明函数noexcept有意义吗?假设这些函数实际上是内联的,这似乎要求编译器在调用站点的函数产生的代码周围生成等效的块,因为如果该区域出现异常,则必须调用。没有inlinenoexcepttryinlineterminatenoexcept,该try块似乎是不必要的。

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

4

2 回答 2

11

让我们假设 noexcept 确实使有意义的代码生成优化成为可能

好的

假设这些函数实际上是内联的,这似乎要求编译器在调用站点的内联函数产生的代码周围生成等效的 try 块,因为如果该区域出现异常

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

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

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

有了这个假设,声明内联函数 noexcept 有意义吗?

是的,为了得到任何假设的优化是noexcept.

于 2014-01-30T17:34:39.563 回答
3

值得注意的是,在权力圈子里有一个关于 nothrow 相关问题的有趣讨论。我强烈推荐阅读这些:

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

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

显然,相当有影响力的人有兴趣在 C++ 中添加某种自动 nothrow 演绎。

经过一番思考后,我将位置更改为几乎相反,见下文

考虑一下:

  • 当您调用具有声明noexcept的函数时-您会从中受益(无需处理可展开性等)

  • 当编译器编译一个具有定义的函数时——(除非编译器可以证明该函数确实noexceptnothrow)性能会受到影响(现在编译器需要确保没有异常可以逃脱这个函数)。您要求它执行无例外承诺

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

现在应该很明显了——声明内联函数是没有意义noexcept的(请记住,并非每个内联函数都会被内联)。

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

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

  2. 非内联,编译器无法证明它不会抛出 -noexcept会伤害函数体,但有利于调用站点(很难说什么更有益)

  3. 内联,编译器可以证明它不会抛出 -noexcept没有用

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

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

noexcept关键字最终被用作承诺(声明时)和强制执行(定义时)——我认为这不是一个完美的方法(哈哈,第二次尝试异常规范,我们仍然没有做对)。

那么该怎么办?

  1. 声明你的行为(唉,语言在这里没有任何帮助)!例如:

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

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

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

  5. noexcept如果您不信任您的函数所做的调用并且宁愿死而不是让它表现出意外,请使用

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

那么,还能怎么noexcept设计呢?

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

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

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

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

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

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

于 2016-08-31T06:23:45.703 回答