关于c ++:我什么时候应该使用noexcept?

When should I really use noexcept?

noexcept关键字可以适当地应用于许多函数签名,但我不确定在实践中什么时候应该考虑使用它。根据我目前所读到的内容,在最后一分钟添加noexcept似乎可以解决移动构造函数抛出时出现的一些重要问题。然而,对于一些实际问题,我仍然无法给出令人满意的答案,这些问题使我首先阅读了更多关于noexcept的内容。

  • 有许多我知道永远不会抛出的函数的例子,但是编译器不能自己决定。在所有这些情况下,我应该在函数声明中附加noexcept吗?

    在每个函数声明之后,必须考虑是否需要附加noexcept,这将大大降低程序员的工作效率(坦率地说,这将是一个麻烦)。在哪些情况下,我应该更小心使用noexcept,在哪些情况下,我可以避开隐含的noexcept(false)

  • 在使用noexcept之后,我什么时候可以实际地期望观察到性能的改善?特别地,给出一个代码示例,其中C++编译器能够在添加EDCOX1〔0〕之后生成更好的机器代码。

    就我个人而言,我关心noexcept,因为它增加了编译器安全应用某些优化的自由度。现代编译器是否以这种方式利用noexcept?如果没有,我能指望他们中的一些人在不久的将来这样做吗?


  • 我认为现在就给出"最佳实践"的答案还为时过早,因为还没有足够的时间在实践中使用它。如果在抛出说明符出现之后就被问到这一点,那么答案将与现在大不相同。

    Having to think about whether or not I need to append noexcept after every function declaration would greatly reduce programmer productivity (and frankly, would be a pain).

    好吧,当函数显然永远不会抛出时就使用它。

    When can I realistically expect to observe a performance improvement after using noexcept? [...] Personally, I care about noexcept because of the increased freedom provided to the compiler to safely apply certain kinds of optimizations.

    似乎最大的优化收益来自用户优化,而不是编译器优化,因为有可能检查noexcept并在上面超载。如果不抛出异常处理方法,大多数编译器都不会受到惩罚,因此我怀疑它会在代码的机器代码级别上改变很多(或任何)东西,尽管可能通过删除处理代码来减小二进制大小。

    在Big4中使用noexcept(构造器、赋值,而不是析构函数,因为它们已经是noexcept)可能会导致最好的改进,因为noexcept检查在模板代码(如std容器)中是"常见的"。例如,std::vector不会使用类的move,除非它被标记为noexcept(或者编译器可以用其他方式推断它)。


    正如我最近不断重复的:语义优先。

    增加noexceptnoexcept(true)noexcept(false)是语义学的首要内容。它只是附带条件一些可能的优化。

    作为一个阅读代码的程序员,noexcept的存在类似于const的存在:它帮助我更好地探索可能发生或不可能发生的事情。因此,花一些时间考虑您是否知道函数是否会抛出是值得的。提醒一下,任何类型的动态内存分配都可能抛出。

    好的,现在讨论可能的优化。

    最明显的优化实际上是在库中执行的。C++ 11提供了许多特性,这些特性允许知道函数是EDCOX1(0)与否,标准库实现本身将使用这些特性来支持用户操作的对象上的EDCOX1×0操作,如果可能的话。比如移动语义。

    编译器可能只会从异常处理数据中去掉一点肥肉(可能),因为它必须考虑到您可能撒了谎这一事实。如果标记为noexcept的函数确实抛出,则调用std::terminate

    选择这些语义有两个原因:

    • 即时受益于noexcept,即使依赖项尚未使用它(向后兼容性)
    • 允许在调用理论上可以抛出但对于给定参数不期望的函数时指定noexcept


    这确实会对编译器中的优化器产生巨大的(潜在的)差异。编译器实际上通过函数定义后的空throw()语句以及适当的扩展已经拥有这个特性多年了。我可以向您保证,现代编译器确实利用了这些知识来生成更好的代码。

    几乎编译器中的每一个优化都使用一个函数的"流程图"来解释什么是合法的。流程图由函数的通常称为"块"(具有单个入口和单个出口的代码区域)和块之间的边组成,以指示流可以跳转到何处。NoExcept更改流程图。

    你问了一个具体的例子。考虑此代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void foo(int x) {
        try {
            bar();
            x = 5;
            // other stuff which doesn't modify x, but might throw
        } catch(...) {
            // don't modify x
        }

        baz(x); // or other statement using x
    }

    如果bar标记为noexcept(无法在bar的结尾和catch语句之间执行),则此函数的流程图不同。当标记为noexcept时,编译器确定在baz函数期间x的值为5—x=5块被称为"主宰"baz(x)块,没有从bar()到catch语句的边缘。然后它可以做一些称为"持续传播"的事情来生成更有效的代码。在这里,如果BAZ是内联的,那么使用X的语句也可能包含常量,然后可以将以前的运行时评估转化为编译时评估,等等。

    总之,简短的回答是:noexcept允许编译器生成更紧密的流图,流图用于解释各种常见的编译器优化。对于编译器来说,这种性质的用户注释是非常棒的。编译器会试图解决这些问题,但通常无法解决(有问题的函数可能在另一个对编译器不可见的对象文件中,或者可传递地使用一些不可见的函数),或者当它确实存在一些小的异常时,可能会引发一些您甚至不知道的异常,因此它无法隐式地将其标记为noexcept(例如,分配内存可能会抛出坏的alloc)。


    noexcept可以显著提高某些操作的性能。这不是在编译器生成机器代码的级别上发生的,而是通过选择最有效的算法:正如其他人提到的,您使用函数std::move_if_noexcept进行选择。例如,std::vector的增长(例如,当我们称为reserve时)必须提供强有力的例外安全保证。如果知道T的move构造函数没有抛出,那么它可以只移动每个元素。否则,它必须复制所有T。本文对此进行了详细描述。


    When can I realistically except to observe a performance improvement after using noexcept? In particular, give an example of code for which a C++ compiler is able to generate better machine code after the addition of noexcept.

    嗯,从来没有?从来都不是吗?从未。

    noexcept用于编译器性能优化,与const用于编译器性能优化的方式相同。也就是说,几乎从来没有。

    noexcept主要用于允许"you"在编译时检测函数是否可以抛出异常。记住:大多数编译器不会为异常发出特殊的代码,除非它实际上抛出了一些东西。因此,noexcept并不是给编译器关于如何优化函数的提示,而是给你关于如何使用函数的提示。

    move_if_noexcept这样的模板将检测move构造函数是否是用noexcept定义的,如果不是,则返回const&而不是类型的&&。这是一种说法,如果这样做是非常安全的话,就要采取行动。

    一般来说,当您认为这样做确实有用时,应该使用noexcept。如果该类型的is_nothrow_constructible为真,则某些代码将采用不同的路径。如果您使用的代码可以做到这一点,那么对于noexcept适当的构造器就可以自由使用。

    简而言之:将它用于移动构造函数和类似的构造,但不要觉得必须对它发疯。


  • There are many examples of functions that I know will never throw, but for which the compiler cannot determine so on its own. Should I append noexcept to the function declaration in all such cases?
  • noexcept棘手的部分是,它的接口函数。特别是,如果你写的代码库,你的客户可以依靠noexcept物业。它可以是很难改变它后,你可能会打破现有的代码。这可能是少关注当你执行你的代码是由只使用应用程序。

    如果你有一个功能让你不能扔,它将在什么样的未来或是restrict noexcept会实现吗?例如,你可能想通过介绍错误检查非法受抛异常(例如,在单元测试),或者你可能取决于其他库代码,可以改变其异常的规范。在这样的实例,它是安全的和omit noexcept是保守的。

    在其他的手,如果你是confident函数应该是不正确的,它是把与它的规范的一部分,你应该把它noexcept。然而,保持心灵的,编译器将不能够实现,如果你noexceptviolations)检测的变化。

  • For which situations should I be more careful about the use of noexcept, and for which situations can I get away with the implied noexcept(false)?
  • 有四类功能,应该你应该因为他们的在线浓缩可能将有最大的影响。

  • 移动业务(移动运营商和移动构造函数分配)
  • 交换业务
  • 内存(deallocators删除删除算子,算子的[ ])
  • destructors(虽然这些都是implicitly noexcept(true)除非你让他们noexcept(false))
  • 这些功能通常是noexcept应该是最有可能与它的使用可以使图书馆实现noexcept物业。例如,可以使用非抛std::vector移动操作没有sacrificing强异常保证。否则,它将必须回来到复制元素(因为它是在C + +(98)。

    这是一种优化的algorithmic水平和不依赖于optimizations编译器。它可以有一个显着的影响,尤其是昂贵的,如果复制的元素。

  • When can I realistically expect to observe a performance improvement after using noexcept? In particular, give an example of code for which a C++ compiler is able to generate better machine code after the addition of noexcept.
  • 对异常的优势在noexcept规范或标准的throw()那是更多的自由时,它允许编译器来退栈。即使在throw()实例,编译完全unwind堆栈(SAH)和它做反向的对象构造的精确阶)。

    在另一noexcept在线的情况下,是不需要的,它是.有一个要求是在堆栈已被解开的(但仍然是允许编译器做的)。这是自由的,它允许进一步的代码优化lowers高架)总是能够被unwind堆栈。

    相关的问题是noexcept栈展开,去到更多的细节和性能的开销在栈展开时是必需的。

    我也建议斯科特迈尔斯的"现代的C + +有效的图书"、"项目14:如果函数声明noexcept不会散发例外"为进一步的阅读。


    在比昂的话:

    Where termination is an acceptable response, an uncaught exception
    will achieve that because it turns into a call of terminate()
    (§13.5.2.5). Also, a noexcept specifier (§13.5.1.1) can make that
    desire explicit.

    Successful fault-tolerant systems are multilevel. Each level copes
    with as many errors as it can without getting too contorted and leaves
    the rest to higher levels. Exceptions support that view. Furthermore,
    terminate() supports this view by providing an escape if the
    exception-handling mechanism itself is corrupted or if it has been
    incompletely used, thus leaving exceptions uncaught. Similarly,
    noexcept provides a simple escape for errors where trying to recover
    seems infeasible.

    1
    2
    3
    4
    5
     double compute(double x) noexcept;   {      
         string s ="Courtney and Anya";
         vector<double> tmp(10);      
         // ...  
     }

    The vector constructor may fail to acquire memory for its ten doubles
    and throw a std::bad_alloc. In that case, the program terminates. It
    terminates unconditionally by invoking std::terminate() (§30.4.1.3).
    It does not invoke destructors from calling functions. It is
    implementation-defined whether destructors from scopes between the
    throw and the noexcept (e.g., for s in compute()) are invoked. The
    program is just about to terminate, so we should not depend on any
    object anyway. By adding a noexcept specifier, we indicate that our
    code was not written to cope with a throw.


    There are many examples of functions that I know will never throw, but for which the compiler cannot determine so on its own. Should I append noexcept to the function declaration in all such cases?

    当您说"我知道[他们]永远不会抛出"时,您的意思是检查函数的实现,您知道函数不会抛出。我认为这种方法是彻底的。

    最好考虑函数是否可以抛出异常作为函数设计的一部分:与参数列表一样重要,以及方法是否是赋值函数(…const。声明"此函数从不抛出异常"是对实现的约束。省略它并不意味着函数可能引发异常;它意味着函数的当前版本和所有未来版本都可能引发异常。这是一个使实现更加困难的约束。但有些方法必须具有约束才能实际使用;最重要的是,它们可以从析构函数调用,也可以在提供强异常保证的方法中实现"回滚"代码。