noexcept关键字可以适当地应用于许多函数签名,但我不确定在实践中什么时候应该考虑使用它。根据我目前所读到的内容,在最后一分钟添加noexcept似乎可以解决移动构造函数抛出时出现的一些重要问题。然而,对于一些实际问题,我仍然无法给出令人满意的答案,这些问题使我首先阅读了更多关于noexcept的内容。
有许多我知道永远不会抛出的函数的例子,但是编译器不能自己决定。在所有这些情况下,我应该在函数声明中附加noexcept吗?
在每个函数声明之后,必须考虑是否需要附加noexcept,这将大大降低程序员的工作效率(坦率地说,这将是一个麻烦)。在哪些情况下,我应该更小心使用noexcept,在哪些情况下,我可以避开隐含的noexcept(false)?
在使用noexcept之后,我什么时候可以实际地期望观察到性能的改善?特别地,给出一个代码示例,其中C++编译器能够在添加EDCOX1〔0〕之后生成更好的机器代码。
就我个人而言,我关心noexcept,因为它增加了编译器安全应用某些优化的自由度。现代编译器是否以这种方式利用noexcept?如果没有,我能指望他们中的一些人在不久的将来这样做吗?
- 代码是用move_if_nothrow(或东西)如果将国有企业绩效的改善有noexcept ctor移动。
- 相关:github.com isocpp / / / / /主斑点cppcoreguidelines & hellip;
- 这是move_if_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(或者编译器可以用其他方式推断它)。
- 我认为std::terminate技巧仍然遵循零成本模式。也就是说,如果使用throw而不是堆栈放卷机,那么noexcept函数中的指令范围被映射为调用std::terminate。因此,我怀疑常规异常跟踪的开销会更大。
- 例如,STD::向量不会使用你的类的移动,除非它被标记为"除了"。真的?您确定这是必需的吗?
- @Klaim看到这个:stackoverflow.com/a/10128180/964135实际上它必须是非抛出的,但是noexcept保证了这一点。
- @好的,谢谢你的澄清,这是一个非常有趣的微妙之处。这就解释了为什么我的代码仍然会移动。
- "如果一个noexcept函数抛出,那么就调用std::terminate,这似乎需要少量的开销"…不,这应该通过不为该函数生成异常表来实现,异常调度器应该捕获该异常表,然后退出。
- @是的,我想我想说的是,它不会完全删除异常处理。有什么建议我应该把文章中的句子改成什么?
- PUBBY C++异常处理通常是在没有开销的情况下完成的,而跳转表将潜在的调用站点地址映射到处理程序入口点。删除这些表与完全删除异常处理非常接近。唯一的区别是可执行文件的大小。可能不值得一提。
- 为什么他们不把noexcept作为默认参数?
- "那么,当函数显然永远不会抛出时,就使用它。"我不同意。noexcept是函数接口的一部分;您不应该仅仅因为当前的实现没有抛出而添加它。我不确定这个问题的正确答案,但我很有信心,你今天的功能表现与此无关…
- @ PATATOSWATER:"C++异常处理通常是没有开销的,除了跳转表"——根据一些编译器作者,不正确;例如,在MeTeNCPCP.COM/index .php/Br/Tys/Injul-to No.-C.H.ZWNJ;和钱德勒8203;mL。搜索,知道控制流是线性的,当然可以启用一些优化,至少在原理。
- @尼莫也看到我最近的答案,stackoverflow.com/questions/26079903/…
- @尼莫任何可能的优化无一例外仍然可能有例外,这是一个明显的观察。
- @好奇的家伙:错了。如果你对编译器优化有任何了解的话,想想例子是很简单的…不过,我只会推荐你访问akrzemi1.wordpress.com/2014/04/24/noexcept-what-for,并建议你自己多读点东西。
- @Nemo引用没有描述任何编译器优化。
- 此功能是否足以创建关键字?
正如我最近不断重复的:语义优先。
增加noexcept、noexcept(true)和noexcept(false)是语义学的首要内容。它只是附带条件一些可能的优化。
作为一个阅读代码的程序员,noexcept的存在类似于const的存在:它帮助我更好地探索可能发生或不可能发生的事情。因此,花一些时间考虑您是否知道函数是否会抛出是值得的。提醒一下,任何类型的动态内存分配都可能抛出。
好的,现在讨论可能的优化。
最明显的优化实际上是在库中执行的。C++ 11提供了许多特性,这些特性允许知道函数是EDCOX1(0)与否,标准库实现本身将使用这些特性来支持用户操作的对象上的EDCOX1×0操作,如果可能的话。比如移动语义。
编译器可能只会从异常处理数据中去掉一点肥肉(可能),因为它必须考虑到您可能撒了谎这一事实。如果标记为noexcept的函数确实抛出,则调用std::terminate。
选择这些语义有两个原因:
- 即时受益于noexcept,即使依赖项尚未使用它(向后兼容性)
- 允许在调用理论上可以抛出但对于给定参数不期望的函数时指定noexcept。
- 也许我是天真的,但我可以想象一个只调用noexcept函数的函数不需要做任何特殊的事情,因为任何可能出现的异常都会在它们达到这个级别之前触发terminate。这与处理和传播bad_alloc异常有很大不同。
- @你说得对。这就是为什么一个足够聪明的编译器可能会发现这一点,并避免在程序计数器/异常处理程序表中为这个函数生成条目(从而如我所说减少了FAT)。然而,许多函数将不会被标记为noexcept(例如,考虑系统调用或低级操作系统特定的C函数),这就是为什么选择的快捷方式是编译器不必检查它。
- 是的,可以按照您建议的方式定义noexcept,但这将是一个真正不可用的特性。如果某些条件不成立,许多函数都可以抛出,即使您知道这些条件已经满足,也无法调用它们。例如,任何可能抛出STD:的函数:
- @TR3W:我理解你的论点,但是我反对现实世界的做法=>很容易误解这个要求,并且有功能抛出,所以不允许它处于noexcept功能中(至少在没有适当的尝试/捕获的情况下)。即使现在它可以工作,任何数量的重构/维护都可能在以后使其失效。这是编译时检查的最大优势:它们是详尽的,并且可以保存一个又一个版本。
- 由于编译器的更改通常要求重新构建所有内容,为什么不使用名称管理,因此将方法foo_whatever声明为noexcept将同时定义foo_whatever和__noex__foo_whatever,而定义没有noexcept的方法将只定义前者。如果一个noexcept方法调用foo,编译器应该让它调用__noex_foo_whatever,并为__noex_foo_whatever生成一个弱定义,该定义调用foo_whatever,如果抛出异常则终止?那么一个只调用其他noexcept方法的noexcept可以避免所有开销。
- @ MatthieuM。回信有点晚了,但无论如何。标记为noexcept的函数可以调用可以引发的其他函数,其承诺是此函数不会发出异常,即它们只需自己处理异常!
- 或者传递参数以确保被调用方不会抛出,即使它用于其他输入——编译器不够聪明或者没有足够的信息来检查,但是程序员在一个好的日子里是这样做的。您并不总是希望根据调用者是否以某种方式预先验证了输入来编写函数的"except"和"noexcept"版本,比如operator[]和at()。
- 我很早以前就对这个答案投了反对票,但在阅读和思考了更多之后,我有了一个评论/问题。""移动语义"是我见过的唯一一个例子,在这里,noexcept显然是一个好主意。我开始认为移动建设,移动分配和交换是唯一的情况有…你知道其他人吗?
- @尼莫:在标准库中,它可能是唯一的一个,但是它展示了一个可以在其他地方重用的原则。移动操作是一种暂时将某些状态置于"边缘"的操作,只有当它是noexcept时,才有人可以在以后可以访问的一段数据上自信地使用它。我可以看到这个想法在别处被使用,但是标准库在C++中相当薄,它只是用来优化我认为的元素的副本。
这确实会对编译器中的优化器产生巨大的(潜在的)差异。编译器实际上通过函数定义后的空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)。
- 这在实践中真的有区别吗?这个例子是人为的,因为在x = 5之前没有任何东西可以抛出。如果try块的那部分起到了任何作用,推理就不成立了。
- 我想说,它确实在优化包含try/catch块的函数方面有很大的不同。我举的这个例子虽然做作,但并不详尽。更重要的一点是,noexcept(就像前面的throw()语句)帮助编译生成更小的流程图(更少的边、更少的块),这是许多优化的基础部分。
- 编译器如何识别代码可以引发异常?对数组的访问是否被视为可能的异常?
- @qub1n如果编译器可以看到函数体,它可以查找显式的throw语句,或者其他可以抛出的东西,如new。如果编译器看不到主体,那么它必须依赖于noexcept的存在或不存在。普通数组访问通常不会生成异常(C++没有边界检查),所以,数组访问不会单独导致编译器认为函数抛出异常。(绑定外访问是ub,不是保证的异常。)
- "CDHOWIE"必须依赖于"NO+"的存在或不存在,除非"除C++之外"中存在throw()。
- @terryhaffey"包含try/catch块的优化函数的实际差异"…它完全处理异常,而不是Java"最后",就像重新抛出的块一样,这是LIB代码中的99%次尝试/捕获。
noexcept可以显著提高某些操作的性能。这不是在编译器生成机器代码的级别上发生的,而是通过选择最有效的算法:正如其他人提到的,您使用函数std::move_if_noexcept进行选择。例如,std::vector的增长(例如,当我们称为reserve时)必须提供强有力的例外安全保证。如果知道T的move构造函数没有抛出,那么它可以只移动每个元素。否则,它必须复制所有T。本文对此进行了详细描述。
- 附录:这意味着,如果定义了移动构造函数或移动分配运算符,则向它们添加noexcept(如果适用)!隐式定义的移动成员函数自动添加了noexcept(如果适用)。
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适当的构造器就可以自由使用。
简而言之:将它用于移动构造函数和类似的构造,但不要觉得必须对它发疯。
- 严格来说,move_if_noexcept不会返回副本,它将返回常量值引用,而不是右值引用。一般来说,这将导致调用者复制而不是移动,但move_if_noexcept没有进行复制。否则,解释得很好。
- + 1乔纳森。例如,如果move构造函数是noexcept,那么调整向量的大小将移动对象,而不是复制它们。所以"从不"不是真的。
- @不过,这并不是编译器优化,这正是他所要求的。
- 我的意思是,编译器在这种情况下会生成更好的代码。OP要求提供一个编译器能够为其生成更优化的应用程序的示例。这似乎是事实(即使它不是编译器优化)。
- @mfontanini:编译器只能生成更好的代码,因为编译器必须编译不同的代码路径。它的工作仅仅是因为std::vector是为了强制编译器编译不同的代码而编写的。这不是关于编译器检测到什么;而是关于用户代码检测到什么。
- "给出一个C++编译器能够在添加No之后生成更好的机器代码的代码的例子,"-->我知道编译器不是更有效地做事情的编译器错误,但毕竟是编译器生成的。
- 由于库是C++标准的一个特定部分,我并不真正关心它是编译器还是库,只要标记我的移动构造函数EDCOX1(2)就可以在所有实现中提供更快的代码。
- @mfontanini:"毕竟是编译器生成它。"根据这种逻辑,使用更好的算法是一种编译器优化。任何东西都是编译器优化。因此,这个术语没有有用的含义。
- 问题是,我似乎在你的答案开头的引号中找不到"编译器优化"。正如@Christianrau所说,编译器生成的代码效率更高,这与优化的起源无关。毕竟,编译器正在生成一个更有效的代码,不是吗?附言:我从来没有说过这是一个编译器优化,我甚至说"这不是一个编译器优化"。
- @尼古拉斯,我觉得你在争论语义学。我知道不是编译器能生成更好的代码。我也从概念上知道你想说什么,在C++中争论语义并不是一件坏事。但老实说,move_if_noexcept正是问题所要寻找的东西。如果严格地说编译器或库不重要,因为从实际的观点来看,在这种情况下,对于任何不真正开发C++标准库的用户来说,都是一样的,几乎没有人会这样做。
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不会散发例外"为进一步的阅读。
- 不过,如果在C++中实现了异常,如Java中标记异常的方法,则可以用EDCOX1×2"关键字"代替EDCOX1(0)否定的方法。我只是不能得到一些C++的设计选择…
- 他们把它命名为以东十一〔0〕,因为以东十一〔5〕已经被占领了。简单地说,throw几乎可以像你所说的那样使用,只是它们把它的设计弄糟了,所以它几乎变成无用的,甚至是有害的。但我们现在仍然坚持这一点,因为移除它将是一个破坏性的变化,而且几乎没有什么好处。所以noexcept基本上就是throw_v2。
- throw如何不有用?
- "好奇"家伙"扔"本身(抛出异常)是有用的,但是"抛出"作为异常说明符已经被弃用,并且在C++ 17中甚至被删除。有关异常说明符不可用的原因,请参阅以下问题:stackoverflow.com/questions/88573/…
- @philipcla&223;en the throw()exception specifier没有提供与nothrow相同的担保?
- @好奇心是的,有一个与编译器优化相关的差异。如果抛出,throw()强制执行堆栈完全解开,而C++ 11 EDCOX1引用0使其对实现开放。这对优化器来说不那么具有约束性。请看这个问题,它也包含了我所指的Scott Meyer的书的引言:stackoverflow.com/q/26079903/783510
- "这种自由允许进一步的代码优化,因为它降低了总是能够展开堆栈的开销。"这与零开销实现没有区别。
在比昂的话:
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.
- 你有这个报价的来源吗?
- "AutangoLoV"C++程序设计语言,第四版"第366页"
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。声明"此函数从不抛出异常"是对实现的约束。省略它并不意味着函数可能引发异常;它意味着函数的当前版本和所有未来版本都可能引发异常。这是一个使实现更加困难的约束。但有些方法必须具有约束才能实际使用;最重要的是,它们可以从析构函数调用,也可以在提供强异常保证的方法中实现"回滚"代码。
- 这是迄今为止最好的答案。您正在向您的方法的用户提供保证,这是另一种表示您将永远限制您的实现(不破坏更改)的方法。谢谢你的启发。
- 也可以看到一个相关的Java问题。