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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10787766/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:27:29  来源:igfitidea点击:

When should I really use noexcept?

c++exceptionexception-handlingc++11noexcept

提问by void-pointer

The noexceptkeyword can be appropriately applied to many function signatures, but I am unsure as to when I should consider using it in practice. Based on what I have read so far, the last-minute addition of noexceptseems to address some important issues that arise when move constructors throw. However, I am still unable to provide satisfactory answers to some practical questions that led me to read more about noexceptin the first place.

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

  1. 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 noexceptto the function declaration in all such cases?

    Having to think about whether or not I need to append noexceptafter everyfunction declaration would greatly reduce programmer productivity (and frankly, would be a pain in the ass). 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)?

  2. 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.

    Personally, I care about noexceptbecause of the increased freedom provided to the compiler to safely apply certain kinds of optimizations. Do modern compilers take advantage of noexceptin this way? If not, can I expect some of them to do so in the near future?

  1. 我知道有许多函数示例永远不会抛出,但编译器无法自行确定。noexcept所有这些情况下,我应该附加到函数声明吗?

    不得不考虑是否需要noexcept每个函数声明之后追加会大大降低程序员的工作效率(坦率地说,这会很痛苦)。在哪些情况下我应该更小心地使用noexcept,哪些情况下我可以摆脱隐含的noexcept(false)

  2. 使用 后,我什么时候可以真正期望观察到性能改进noexcept?特别是,给出一个代码示例,在添加noexcept.

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

采纳答案by Pubby

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 noexceptafter 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? [...] Personally, I care about noexceptbecause of the increased freedom provided to the compiler to safely apply certain kinds of optimizations.

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

It seems like the biggest optimization gains are from user optimizations, not compiler ones due to the possibility of checking noexceptand overloading on it. 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.

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

Using noexceptin the big four (constructors, assignment, not destructors as they're already noexcept) will likely cause the best improvements as noexceptchecks are 'common' in template code such as in stdcontainers. For instance, std::vectorwon't use your class's move unless it's marked noexcept(or the compiler can deduce it otherwise).

使用noexcept在四大(构造函数,赋值,析构函数不是因为他们已经noexcept)很可能会造成最好的改进如noexcept支票模板代码中“普通”,如std容器。例如,std::vector除非被标记noexcept(否则编译器可以推断出它),否则不会使用您的类的移动。

回答by Matthieu M.

As I keep repeating these days: semantics first.

正如我这些天不断重复的那样:语义第一

Adding noexcept, noexcept(true)and noexcept(false)is first and foremost about semantics. It only incidentally condition a number of possible optimizations.

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

As a programmer reading code, the presence of noexceptis akin to that of const: it helps me better grok what may or may not happen. 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.

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



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 noexceptor not, and the Standard Library implementation themselves will use those traits to favor noexceptoperations on the user-defined objects they manipulate, if possible. Such as move semantics.

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

The compiler may only shave a bit of fat (perhaps) from the exception handling data, because it hasto take into account the fact that you may have lied. If a function marked noexceptdoes throw, then std::terminateis called.

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

These semantics were chosen for two reasons:

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

  • immediately benefiting from noexcepteven when dependencies do not use it already (backward compatibility)
  • allowing the specification of noexceptwhen calling functions that may theoretically throw, but are not expected to for the given arguments
  • noexcept即使依赖项尚未使用它,也可以立即受益(向后兼容性)
  • 允许noexcept在调用理论上可能抛出的函数时进行规范,但对于给定的参数预计不会

回答by Terry Mahaffey

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. I can assure you that modern compilers do take advantage of this knowledge to generate better code.

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

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 baris labeled noexcept(there is no way for execution to jump between the end of barand the catch statement). 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.

这个函数的流程图是不同的 ifbar被标记了noexcept(没有办法让执行在结束bar和 catch 语句之间跳转)。当标记为 时noexcept,编译器确定在 baz 函数期间 x 的值是 5 - x=5 块被称为“支配” baz(x) 块,而没有 from bar()to 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: noexceptlets the compiler generate a tighter flow graph, and the flow graph is used to reason about all sorts of common compiler optimizations. 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让编译器生成更紧密的流程图,并且流程图用于推理各种常见的编译器优化。对于编译器来说,这种性质的用户注释很棒。编译器会尝试弄清楚这些东西,但它通常不能(有问题的函数可能位于编译器不可见的另一个目标文件中,或者传递使用某些不可见的函数),或者当它这样做时,有一些可能会抛出您甚至都不知道的微不足道的异常,因此它不能隐式地将其标记为noexcept(例如,分配内存可能会抛出 bad_alloc)。

回答by Andrzej

noexceptcan dramatically improve performance of some operations. This does not happen at the level of generating machine code by the compiler, but by selecting the most effective algorithm: as others mentioned, you do this selection using function std::move_if_noexcept. For instance, the growth of std::vector(e.g., when we call reserve) must provide a strong exception-safety guarantee. If it knows that T's move constructor doesn't throw, it can just move every element. Otherwise it must copy all Ts. This has been described in detail in this post.

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

回答by Nicol Bolas

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?特别地,给出一个代码示例,在添加 noexcept 后,C++ 编译器能够为其生成更好的机器代码。

Um, never? Is never a time? Never.

嗯,从来没有?从来没有时间吗?绝不。

noexceptis for compilerperformance optimizations in the same way that constis for compiler performance optimizations. That is, almost never.

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

noexceptis primarily used to allow "you" to detect at compile-time if a function can throw an exception. Remember: most compilers don't emit special code for exceptions unless it actually throws something. So noexceptis not a matter of giving the compiler hints about how to optimize a function so much as giving youhints about how to use a function.

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

Templates like move_if_noexceptwill detect if the move constructor is defined with noexceptand will return a const&instead of a &&of the type if it is not. It's a way of saying to move if it is very safe to do so.

模板 likemove_if_noexcept将检测是否定义了移动构造函数,如果不是,noexcept则返回类型的 aconst&而不是 a &&。如果这样做非常安全,这是一种移动方式。

In general, you should use noexceptwhen you think it will actually be usefulto do so. Some code will take different paths if is_nothrow_constructibleis true for that type. If you're using code that will do that, then feel free to noexceptappropriate constructors.

一般来说,您应该noexcept在您认为这样做实际上有用时使用。如果is_nothrow_constructible该类型为真,则某些代码将采用不同的路径。如果您使用的代码可以做到这一点,那么请随意使用noexcept适当的构造函数。

In short: use it for move constructors and similar constructs, but don't feel like you have to go nuts with it.

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

回答by Saurav Sahu

In Bjarne's words (The C++ Programming Language, 4th Edition, page 366):

Bjarne的话(C++ 编程语言,第 4 版,第 366 页):

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 noexceptspecifier (§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 corruptedor if it has been incompletely used, thus leaving exceptions uncaught. Similarly, noexceptprovides a simple escape for errors where trying to recover seems infeasible.

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 throwand 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 noexceptspecifier, we indicate that our code was not written to cope with a throw.

如果终止是可接受的响应,则未捕获的异常将实现这一点,因为它变成了对 terminate() 的调用(第 13.5.2.5 节)。此外,noexcept说明符(第 13.5.1.1 节)可以使该愿望明确。

成功的容错系统是多层次的。每个级别都会处理尽可能多的错误,而不会过于扭曲,而将其余部分留给更高级别。例外支持这种观点。此外, terminate()如果异常处理机制本身已损坏或未完全使用,则通过提供转义来支持此观点,从而使异常未被捕获。同样, noexcept为尝试恢复似乎不可行的错误提供简单的转义。

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

向量构造函数可能无法为其十个双精度获取内存并抛出一个std::bad_alloc. 在这种情况下,程序终止。它通过调用std::terminate()(§30.4.1.3)无条件终止。它不会从调用函数中调用析构函数。是否调用来自 thethrow和 the之间的作用域的析构函数noexcept(例如,for s in compute())是实现定义的 。程序即将终止,所以无论如何我们都不应该依赖任何对象。通过添加noexcept说明符,我们表明我们的代码不是为了处理抛出而编写的。

回答by Philipp Cla?en

  1. 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?
  1. 我知道有许多函数示例永远不会抛出,但编译器无法自行确定。在所有这些情况下,我应该将 noexcept 附加到函数声明中吗?

noexceptis tricky, as it is part of the functions interface. Especially, if you are writing a library, your client code can depend on the noexceptproperty. It can be difficult to change it later, as you might break existing code. That might be less of a concern when you are implementing code that is only used by your application.

noexcept很棘手,因为它是函数接口的一部分。特别是,如果您正在编写一个库,您的客户端代码可能依赖于该noexcept属性。以后可能很难更改它,因为您可能会破坏现有代码。当您实现仅由您的应用程序使用的代码时,这可能不太重要。

If you have a function that cannot throw, ask yourself whether it will like stay noexceptor would that restrict future implementations? For example, you might want to introduce error checking of illegal arguments by throwing exceptions (e.g., for unit tests), or you might depend on other library code that could change its exception specification. In that case, it is safer to be conservative and omit noexcept.

如果你有一个不能抛出的函数,问问自己它是否会喜欢停留noexcept还是会限制未来的实现?例如,您可能希望通过抛出异常(例如,用于单元测试)来引入对非法参数的错误检查,或者您可能依赖其他可能更改其异常规范的库代码。在这种情况下,保守并省略 更安全noexcept

On the other hand, if you are confident that the function should never throw and it is correct that it is part of the specification, you should declare it noexcept. However, keep in mind that the compiler will not be able to detect violations of noexceptif your implementation changes.

另一方面,如果您确信该函数不应该抛出异常并且它是规范的一部分是正确的,那么您应该声明它noexcept。但是,请记住,noexcept如果您的实现发生更改,编译器将无法检测到违规行为。

  1. 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)?
  1. 在哪些情况下我应该更小心地使用 noexcept,哪些情况下我可以使用隐含的 noexcept(false)?

There are four classes of functions that should you should concentrate on because they will likely have the biggest impact:

您应该关注四类功能,因为它们可能会产生最大的影响:

  1. move operations (move assignment operator and move constructors)
  2. swap operations
  3. memory deallocators (operator delete, operator delete[])
  4. destructors (though these are implicitly noexcept(true)unless you make them noexcept(false))
  1. 移动操作(移动赋值运算符和移动构造函数)
  2. 交换操作
  3. 内存释放器(操作符删除,操作符删除[])
  4. 析构函数(尽管这些都是隐式的,noexcept(true)除非你制作它们noexcept(false)

These functions should generally be noexcept, and it is most likely that library implementations can make use of the noexceptproperty. For example, std::vectorcan use non-throwing move operations without sacrificing strong exception guarantees. Otherwise, it will have to fall back to copying elements (as it did in C++98).

这些函数通常应该是noexcept,并且很可能库实现可以使用该noexcept属性。例如,std::vector可以在不牺牲强异常保证的情况下使用非抛出移动操作。否则,它将不得不退回到复制元素(就像在 C++98 中所做的那样)。

This kind of optimization is on the algorithmic level and does not rely on compiler optimizations. It can have a significant impact, especially if the elements are expensive to copy.

这种优化是在算法层面上的,不依赖于编译器优化。它可能会产生重大影响,尤其是在元素复制成本高昂的情况下。

  1. 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.
  1. 使用 noexcept 后,我​​什么时候可以真正期望观察到性能改进?特别是,给出一个代码示例,在添加 noexcept 后,C++ 编译器能够为其生成更好的机器代码。

The advantage of noexceptagainst no exception specification or throw()is that the standard allows the compilers more freedom when it comes to stack unwinding. Even in the throw()case, the compiler has to completely unwind the stack (and it has to do it in the exact reverse order of the object constructions).

noexcept没有异常规范的优点throw()是标准允许编译器在堆栈展开时有更多的自由。即使在这种throw()情况下,编译器也必须完全展开堆栈(并且必须以与对象构造完全相反的顺序进行)。

In the noexceptcase, on the other hand, it is not required to do that. There is no requirement that the stack has to be unwound (but the compiler is still allowed to do it). That freedom allows further code optimization as it lowers the overhead of always being able to unwind the stack.

noexcept另一方面,在这种情况下,不需要这样做。没有要求必须解开堆栈(但仍然允许编译器这样做)。这种自由允许进一步的代码优化,因为它降低了始终能够展开堆栈的开销。

The related question about noexcept, stack unwinding and performancegoes into more details about the overhead when stack unwinding is required.

关于noexcept、堆栈展开和性能的相关问题详细介绍了需要堆栈展开时的开销。

I also recommend Scott Meyers book "Effective Modern C++", "Item 14: Declare functions noexcept if they won't emit exceptions" for further reading.

我还推荐 Scott Meyers 的书“Effective Modern C++”、“Item 14:Declare functions noexcept if they won't Emission exceptions”以供进一步阅读。

回答by Raedwald

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 designof the function: as important as the argument list and whether a method is a mutator (... 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 andall 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.

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