当前位置: 首页 > 知识库问答 >
问题:

什么时候我应该真正使用noexcept?

墨翔宇
2023-03-14

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

>

  • 有许多函数的例子,我知道它们永远不会抛出,但编译器无法自行确定这些函数。在所有这种情况下,我是否应该在函数声明中追加noexcept

    必须考虑是否需要在每个函数声明后添加noexcept会大大降低程序员的工作效率(坦率地说,这将是一种痛苦)。对于哪些情况,我应该更小心使用noexcept;对于哪些情况,我可以使用隐含的noexcept(false)

    在使用noexcept后,我什么时候可以实际地看到性能改进?特别是,给出一个代码示例,在添加noexcept之后,C++编译器能够生成更好的机器代码。

    就我个人而言,我关心noexcept,因为编译器安全地应用某些类型的优化提供了更多的自由度。现代编译器是否以这种方式利用noexcept?如果不是,我是否可以期望他们中的一些人会在不久的将来这样做?

  • 共有3个答案

    况明贤
    2023-03-14

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

    几乎编译器中的每一个优化都使用一个叫做函数“流图”的东西来推理什么是合法的。流图由通常称为函数的“块”(具有单个入口和单个出口的代码区域)和块之间的边组成,这些边指示流可以跳到哪里。Noexcept更改流图。

    你要求一个具体的例子。请考虑以下代码:

    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(例如,分配内存可能会抛出bad_alloc)。

    轩辕煜
    2023-03-14

    这几天我一直在重复:语义第一。

    添加noexceptnoexcept(true)noexcept(false)首先是关于语义的。它只是附带地限制了一些可能的优化。

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

    好了,现在来看看可能的优化。

    最明显的优化实际上是在库中执行的。C++11提供了许多特性,允许了解函数是否为noexcept,如果可能的话,标准库实现本身将使用这些特性来支持对其操作的用户html" target="_blank">定义对象进行noexcept操作。例如移动语义。

    编译器可能只会从异常处理数据中剃掉一点脂肪(也许),因为它必须考虑到您可能说谎的事实。如果标记为noexcept的函数抛出,则调用std::terminate

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

    • 即使依赖项尚未使用noexcept(向后兼容性)
    • 也会立即受益于 noexcept(向后兼容)
    • 允许在调用理论上可能抛出但对于给定参数不期望抛出的函数时使用noexcept的规范
    季骏祥
    2023-03-14

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

    必须考虑是否需要在每个函数声明后添加noexcept会大大降低程序员的工作效率(坦率地说,这将是一种痛苦)。

    好吧,那就在函数显然永远不会抛出的时候使用它。

    在使用noexcept后,我什么时候可以实际地看到性能改进?[...]就我个人而言,我关心noexcept,因为编译器安全地应用某些类型的优化提供了更多的自由度。

    似乎最大的优化收益来自用户优化,而不是编译器优化,因为可以检查noexcept并在其上重载。大多数编译器遵循不抛出就不罚的异常处理方法,所以我怀疑它在代码的机器代码级别上会有多大的改变(或任何改变),尽管可能会通过删除处理代码来减少二进制大小。

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

     类似资料:
    • 问题内容: 有什么区别?什么时候应该使用容量为1的对抗? 问题答案: SynchronousQueue更像是一个传递,而LinkedBlockingQueue仅允许单个元素。区别在于对SynchronousQueue的put()调用直到有相应的take()调用 才返回 ,但LinkedBlockingQueue的大小为1,则put()调用(对空队列)将立即返回。 我不能说自己曾经直接使用过Sync

    • 问题内容: 我对使用和翻译有疑问。我了解到,在模型中,我应该使用。但是还有其他地方我也应该使用吗?表单定义呢?它们之间是否存在性能差异? 编辑: 还有一件事。有时候,代替被使用。正如文档所述,仅在将字符串显示给用户之前,才将字符串标记为要翻译,并在可能的最新情况下进行翻译,但是我在这里有点困惑,这与功能相似吗?我仍然很难决定在模型和表格中应该使用哪个。 问题答案: ugettext() 与 uge

    • 问题内容: 我知道他们两个都禁用了Nagle的算法。 我什么时候应该/不应该使用它们中的每一个? 问题答案: 首先,不是所有人都禁用Nagle的算法。 Nagle的算法用于减少有线中更多的小型网络数据包。该算法是:如果数据小于限制(通常是MSS),请等待直到收到先前发送的数据包的ACK,同时累积用户的数据。然后发送累积的数据。 这将对telnet等应用程序有所帮​​助。但是,在发送流数据时,等待A

    • 问题内容: 在该类中,有两个字符串,和。 有什么不同?我什么时候应该使用另一个? 问题答案: 如果你的意思是和则: 用于在文件路径列表中分隔各个文件路径。考虑在上的环境变量。您使用a分隔文件路径,因此在上将是;。 是或用于拆分到特定文件的路径。例如在上,或

    • 问题内容: 在集成我以前从未使用过的Django应用程序时,我发现了用于定义类中函数的两种不同方式。作者似乎非常有意地使用了它们。第一个是我自己经常使用的: 另一个是我不使用的,主要是因为我不知道何时使用它,以及什么用途: 在Python文档中,装饰器的解释如下: 类方法将类作为隐式第一个参数接收,就像实例方法接收实例一样。 所以我想指的是自己(而不是实例)。我不完全理解为什么会这样,因为我总是可

    • 问题内容: 我仍然是React的菜鸟,在互联网上的许多示例中,我看到了渲染子元素时出现的这种变化,我感到困惑。通常我看到以下内容: 但是然后我看到一个这样的例子: 现在,我了解了api,但是文档并未确切说明我何时应该使用它。 那么,一个人做什么却另一个人不能做什么呢?有人可以用更好的例子向我解释吗? 问题答案: 编辑: 相反,请看Vennesa的答案,这是一个更好的解释。 原版的: 首先,该示例仅