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

  • 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?

    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). 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) ?

  • 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 noexcept because of the increased freedom provided to the compiler to safely apply certain kinds of optimizations. Do modern compilers take advantage of noexcept in this way? If not, can I expect some of them to do so in the near future?


  • 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).

    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 noexcept because of the increased freedom provided to the compiler to safely apply certain kinds of optimizations.

    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. 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 4 (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. For instance, std::vector won't use your class's move unless it's marked noexcept (or the compiler can deduce it otherwise).


    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.

    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. Therefore, it is worthwhile spending some time thinking about whether or not you know if the function will throw. For 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. 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.

    These semantics were chosen for two reasons:

  • immediately benefiting from noexcept even when dependencies do not use it already (backward compatibility)
  • allowing the specification of noexcept when calling functions that may theoretically throw but are not expected to for the given arguments

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

    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.

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

    Anyway, 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. 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).

    链接地址: http://www.djcxy.com/p/73550.html

    上一篇: 使用双逻辑非(!!)运算符困惑

    下一篇: 我应该什么时候使用noexcept?