本文翻译自:When should I really use noexcept?
The noexcept
keyword can be appropriately applied to many function signatures, but I am unsure as to when I should consider using it in practice. noexcept
关键字可以适当地应用于许多函数签名,但是我不确定何时应该考虑在实践中使用它。 Based on what I have read so far, the last-minute addition of noexcept
seems to address some important issues that arise when move constructors throw. 根据我到目前为止所读的内容,在最后一刻添加noexcept
似乎可以解决移动构造函数抛出时出现的一些重要问题。 However, I am still unable to provide satisfactory answers to some practical questions that led me to read more about noexcept
in the first place. 但是,对于某些实际问题,我仍然无法提供令人满意的答案,这些问题使我首先阅读了有关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
吗?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 in the ass). 必须考虑在每个函数声明之后是否都需要附加noexcept
,这将大大降低程序员的工作效率(坦率地说,这将是一件痛苦的事情)。 For which situations should I be more careful about the use ofnoexcept
, and for which situations can I get away with the impliednoexcept(false)
? 在哪些情况下应更谨慎地使用noexcept
,对于哪种情况我可以摆脱隐含的noexcept(false)
?When can I realistically expect to observe a performance improvement after using
noexcept
? 使用noexcept
之后,我什么时候可以实际期望获得性能改善? In particular, give an example of code for which a C++ compiler is able to generate better machine code after the addition ofnoexcept
. 特别是,给出一个示例代码,在添加noexcept
之后,C ++编译器能够为其生成更好的机器代码。Personally, I care about
noexcept
because of the increased freedom provided to the compiler to safely apply certain kinds of optimizations. 就我个人而言,我noexcept
因为为编译器提供了增加的自由度,可以安全地应用某些优化。 Do modern compilers take advantage ofnoexcept
in this way? 现代编译器noexcept
以这种方式利用noexcept
优势? If not, can I expect some of them to do so in the near future? 如果没有,我可以指望其中的一些在不久的将来这样做吗?
#1楼
参考:https://stackoom.com/question/jGOE/我什么时候应该真正使用noexcept
#2楼
When can I realistically except to observe a performance improvement after using
noexcept
? 在使用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之后,C ++编译器能够为其生成更好的机器代码。
Um, never? 嗯,从来没有? Is never a time? 从来没有时间? Never. 决不。
noexcept
is for compiler performance optimizations in the same way that const
is for compiler performance optimizations. noexcept
用于编译器性能优化,与const
用于编译器性能优化的方式相同。 That is, almost never. 也就是说,几乎永远不会。
noexcept
is primarily used to allow "you" to detect at compile-time if a function can throw an exception. noexcept
主要用于允许“您”在编译时检测函数是否可以引发异常。 Remember: most compilers don't emit special code for exceptions unless it actually throws something. 请记住:大多数编译器不会发出特殊的异常代码,除非它实际上抛出了异常。 So noexcept
is not a matter of giving the compiler hints about how to optimize a function so much as giving you hints about how to use a function. 因此, noexcept
不仅是向编译器提供有关如何优化函数的提示,也不仅仅是向您提供有关如何使用函数的提示。
Templates like move_if_noexcept
will detect if the move constructor is defined with noexcept
and will return a const&
instead of a &&
of the type if it is not. 诸如move_if_noexcept
模板将检测是否使用noexcept
定义了move构造函数,如果noexcept
,则将返回const&
而不是类型的&&
。 It's a way of saying to move if it is very safe to do so. 如果这样做很安全,那是一种动议的方式。
In general, you should use noexcept
when you think it will actually be useful to do so. 通常,当您认为使用noexcept
确实有用时,应该使用noexcept
。 Some code will take different paths if is_nothrow_constructible
is true for that type. 如果is_nothrow_constructible
对于该类型为true,则某些代码将采用不同的路径。 If you're using code that will do that, then feel free to noexcept
appropriate constructors. 如果您使用的代码可以做到这一点,那么noexcept
适当的构造函数外, noexcept
。
In short: use it for move constructors and similar constructs, but don't feel like you have to go nuts with it. 简而言之:将其用于move构造函数和类似的构造,但不必觉得必须为此而烦恼。
#3楼
I think it is too early to give a "best practices" answer for this as there hasn't been enough time to use it in practice. 我认为对此给出“最佳实践”答案还为时过早,因为没有足够的时间在实践中使用它。 If this was asked about throw specifiers right after they came out then the answers would be very different to now. 如果这是在抛出异常说明符之后立即问到的,那么答案将与现在大不相同。
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). 必须考虑在每个函数声明之后是否都需要附加noexcept
,这将大大降低程序员的生产率(坦率地说,这很痛苦)。
Well, then use it when it's obvious that the function will never throw. 好吧,然后在明显无法使用该函数的情况下使用它。
When can I realistically expect to observe a performance improvement after using
noexcept
? 使用noexcept
之后,我什么时候可以实际期望获得性能改善? [...] Personally, I care aboutnoexcept
because of the increased freedom provided to the compiler to safely apply certain kinds of optimizations. [...]我个人noexcept
关心,因为为编译器提供了增加的自由度,可以安全地应用某些优化。
It seems like the biggest optimization gains are from user optimizations, not compiler ones due to the possibility of checking noexcept
and overloading on it. 似乎最大的优化收益来自用户优化,而不是编译器优化,原因是可以检查noexcept
并对其进行重载。 Most compilers follow a no-penalty-if-you-don't-throw exception handling method, so I doubt it would change much (or anything) on the machine code level of your code, although perhaps reduce the binary size by removing the handling code. 大多数编译器都遵循“不罚则不扔”的异常处理方法,因此我怀疑它会在代码的机器代码级别上改变很多(或任何东西),尽管可能通过删除处理代码。
Using noexcept
in the big four (constructors, assignment, not destructors as they're already noexcept
) will likely cause the best improvements as noexcept
checks are 'common' in template code such as in std
containers. 在noexcept
中使用noexcept
(构造函数,赋值,而不是析构函数,因为它们已经是noexcept
)可能会带来最佳的改进,因为noexcept
检查在模板代码(例如std
容器)中是“常见的”。 For instance, std::vector
won't use your class's move unless it's marked noexcept
(or the compiler can deduce it otherwise). 例如,除非将std::vector
标记为noexcept
(否则编译器可以推断出它的意思),否则它将不会使用您的类的移动。
#4楼
As I keep repeating these days: semantics first . 正如我最近一直在重复的那样: 语义优先 。
Adding noexcept
, noexcept(true)
and noexcept(false)
is first and foremost about semantics. noexcept
,添加noexcept
, noexcept(true)
和noexcept(false)
是语义上的首要任务。 It only incidentally condition a number of possible optimizations. 它只是附带条件了许多可能的优化。
As a programmer reading code, the presence of noexcept
is akin to that of const
: it helps me better grok what may or may not happen. 作为程序员阅读代码, noexcept
的存在类似于const
:它可以帮助我更好地了解可能发生或可能发生的事情。 Therefore, it is worthwhile spending some time thinking about whether or not you know if the function will throw. 因此,值得花一些时间考虑是否知道该函数是否将抛出。 For a reminder, any kind of dynamic memory allocation may throw. 提醒一下,任何类型的动态内存分配都可能抛出。
Okay, now on to the possible optimizations. 好的,现在继续可能的优化。
The most obvious optimizations are actually performed in the libraries. 最明显的优化实际上是在库中执行的。 C++11 provides a number of traits that allows knowing whether a function is noexcept
or not, and the Standard Library implementation themselves will use those traits to favor noexcept
operations on the user-defined objects they manipulate, if possible. C ++ 11提供了许多特征,这些特征允许知道一个函数是否为noexcept
,并且如果可能的话,标准库实现本身将使用这些特征来支持noexcept
操作的用户定义对象的noexcept
操作。 Such as move semantics . 如移动语义 。
The compiler may only shave a bit of fat (perhaps) from the exception handling data, because it has to take into account the fact that you may have lied. 编译器可能仅从异常处理数据中减少了一些麻烦(也许),因为它必须考虑到您可能撒谎的事实。 If a function marked noexcept
does throw, then std::terminate
is called. 如果标记为noexcept
的函数确实抛出,则将调用std::terminate
。
These semantics were chosen for two reasons: 选择这些语义有两个原因:
- immediately benefiting from
noexcept
even when dependencies do not use it already (backward compatibility) 立即从noexcept
受益,即使依赖项尚未使用它(向后兼容性) - allowing the specification of
noexcept
when calling functions that may theoretically throw, but are not expected to for the given arguments 允许在调用理论上可能会抛出但对给定参数不期望的函数时noexcept
#5楼
This actually does make a (potentially) huge difference to the optimizer in the compiler. 实际上,这确实对编译器中的优化程序产生了(可能)巨大的影响。 Compilers have actually had this feature for years via the empty throw() statement after a function definition, as well as propriety extensions. 多年来,编译器实际上已经通过在函数定义以及适当的扩展之后使用空throw()语句来具有此功能。 I can assure you that modern compilers do take advantage of this knowledge to generate better code. 我可以向您保证,现代编译器确实会利用这些知识来生成更好的代码。
Almost every optimization in the compiler uses something called a "flow graph" of a function to reason about what is legal. 编译器中几乎所有的优化都使用一种称为函数的“流程图”的方法来推理合法的东西。 A flow graph consists of what are generally called "blocks" of the function (areas of code that have a single entrance and a single exit) and edges between the blocks to indicate where flow can jump to. 流程图由函数的通常所谓的“块”(具有单个入口和单个出口的代码区域)和块之间的边缘组成,以指示流可以跳转到何处。 Noexcept alters the flow graph. Noexcept更改流程图。
You asked for a specific example. 您要求一个具体的例子。 Consider this code: 考虑以下代码:
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
}
The flow graph for this function is different if bar
is labeled noexcept
(there is no way for execution to jump between the end of bar
and the catch statement). 如果将bar
标记为noexcept
,则此函数的流程图会有所不同(无法在bar
的末尾和catch语句之间跳转来执行)。 When labeled as noexcept
, the compiler is certain the value of x is 5 during the baz function - the x=5 block is said to "dominate" the baz(x) block without the edge from bar()
to the catch statement. 当标记为noexcept
,编译器可以确定在baz函数期间x的值为5-据说x = 5块“支配”了baz(x)块,而没有bar()
到catch语句的边缘。
It can then do something called "constant propagation" to generate more efficient code. 然后,它可以执行称为“恒定传播”的操作以生成更有效的代码。 Here if baz is inlined, the statements using x might also contain constants and then what used to be a runtime evaluation can be turned into a compile-time evaluation, etc. 在这里,如果内联baz,则使用x的语句也可能包含常量,然后可以将以前是运行时评估的内容转换为编译时评估等。
Anyway, the short answer: noexcept
lets the compiler generate a tighter flow graph, and the flow graph is used to reason about all sorts of common compiler optimizations. 无论如何,简短的答案: noexcept
使编译器生成更紧密的流程图,该流程图用于推理各种常见的编译器优化。 To a compiler, user annotations of this nature are awesome. 对于编译器来说,这种性质的用户注释很棒。 The compiler will try to figure this stuff out, but it usually can't (the function in question might be in another object file not visible to the compiler or transitively use some function which is not visible), or when it does, there is some trivial exception which might be thrown that you're not even aware of, so it can't implicitly label it as noexcept
(allocating memory might throw bad_alloc, for example). 编译器会尝试找出这些东西,但通常不会(有问题的函数可能在另一个目标文件中,编译器不可见,或者可以过渡地使用某些不可见的函数),或者当它出现时,您可能根本没有意识到的一些琐碎的异常,因此它无法将其隐式标记为noexcept
(例如,分配内存可能会抛出bad_alloc)。
#6楼
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吗?
When you say "I know [they] will never throw", you mean by examining the implementation of the function you know that the function will not throw. 当您说“我知道他们永远不会抛出”时,您的意思是通过检查该函数的实现,您知道该函数不会抛出。 I think that approach is inside out. 我认为这种方法是彻底的。
It is better to consider whether a function may throw exceptions to be part of the design of the function: as important as the argument list and whether a method is a mutator (... const
). 最好考虑一个函数是否可能抛出异常以作为该函数设计的一部分:与参数列表同等重要,并且一个方法是否是一个变量(... const
)。 Declaring that "this function never throws exceptions" is a constraint on the implementation. 声明“此函数永不抛出异常”是对实现的约束。 Omitting it does not mean the function might throw exceptions; 省略它并不意味着该函数可能会引发异常。 it means that the current version of the function and all future versions may throw exceptions. 这意味着该函数的当前版本以及所有将来的版本都可能引发异常。 It is a constraint that makes the implementation harder. 这是使实施更加困难的约束。 But some methods must have the constraint to be practically useful; 但是某些方法必须具有一定的约束条件才能实用。 most importantly, so they can be called from destructors, but also for implementation of "roll-back" code in methods that provide the strong exception guarantee. 最重要的是,因此可以从析构函数中调用它们,也可以在提供强大异常保证的方法中实现“回滚”代码。