19

From what I can tell, the SO community is divided on whether declaring a function noexcept enables meaningful compiler optimizations that would not otherwise be possible. (I'm talking specifically about compiler optimizations, not library implementation optimizations based on move_if_noexcept.) For purposes of this question, let's assume that noexcept does make meaningful code-generation optimizations possible. With that assumption, does it make sense to declare inline functions noexcept? Assuming such functions are actually inlined, this would seem to require that compilers generate the equivalent of a try block around the code resulting from the inline function at the call site, because if an exception arises in that region, terminate must be called. Without noexcept, that try block would seem to be unnecessary.

My original interest was in whether it made sense to declare Lambda functions noexcept, given that they are implicitly inline, but then I realized that the same issues arise for any inline function, not just Lambdas.

KnowItAllWannabe
  • 12,972
  • 8
  • 50
  • 91
  • A `try`-block implies stack unwinding. Calling `terminate` does not. – dyp Jan 30 '14 at 17:33
  • 3
    Efficiency concerns aside, this could be situationally useful if the function really should be noexcept. – templatetypedef Jan 30 '14 at 17:33
  • 1
    @dyp: When I wrote "the equivalent of a `try` block," I was trying to express the idea that code would still have to be generated to detect exceptions at runtime and to call `terminate` if they arose. I agree that the generated code need not do exactly what a true `try` block would do. – KnowItAllWannabe Jan 30 '14 at 17:38
  • @KnowItAllWannabe Indeed, and there is potential for optimization. For example, you typically have to track which objects have been constructed successfully, to allow destructing them when an exception is thrown. Inside a `noexcept(true)`-function (that doesn't have `try` blocks) you don't need to do that, you only need to detect if an exception has been thrown. – dyp Jan 30 '14 at 17:40
  • Unluckily, the requirement of calling `terminate` makes `noexcept` exactly the same as `throw()`, except for the petty detail that it _always_ calls `terminate`, never `unexpected`. The only difference between using and not using `noexcept` is "immediately" vs. "eventually". To guarantee the "immediately" bit, the compiler has to add extra code to guarantee this. Of course there is no truly good solution to the problem, but I would personally prefer simply UB (presumably normal unwinding, possibly a wrong handler catching, and eventually defaulting to `terminate`) if you break your promise. – Damon Jan 30 '14 at 17:45
  • All in all, the presence of `noexcept` only makes your life more complicated because in order to benefit from the the much-praised _automatic_ moves in the standard library, you actually have to do an extra dance. – Damon Jan 30 '14 at 17:46
  • @Damon: chances are you intend your move functions not to throw exceptions (just like you did for swaps in C++03). So for almost all classes if they do it's a bug. If you don't want to dance you can: mark moves `noexcept`; do no extra work; take the library benefits; debug your program if it terminates, just as you would with any other buggy class causing undesired behavior. Or if you really truly don't want to dance, don't even mark the moves `noexcept`. You're no worse off than you were in C++03. – Steve Jessop Jan 30 '14 at 17:51
  • @SteveJessop: Thing is, if you break your promise on `const`, you're out alone in the dark and evil things happen. If you break your promise on aliasing, evil things will happen. If you break your promise on `noexcept`, the compiler accounts for it. Does that make sense to you? – Damon Jan 30 '14 at 18:06
  • @Damon: The effect of a violated `noexcept` makes no difference to how much `noexcept`-dancing I do in my code: either way I don't want to violate it. So I don't care until compiler-writers come and tell me that my code would run N% faster if a violated `noexcept` was UB instead of terminating. At that point, like you I'd want a `properly_noexcept` qualifier that tells the `noexcept` operator the function is `noexcept`, but doesn't require exception-handling. How disgruntled are the authors of GCC and Clang with the adopted `noexcept` definition? – Steve Jessop Jan 30 '14 at 18:11
  • 1
    @KnowItAllWannabe It's likely necessary to generate the equivalent of a try block even if the inlined function isn't `noexcept`. Since the runtime cost of a try block is 0, however, this shouldn't be an issue. – James Kanze Jan 30 '14 at 19:00
  • How will the inlining change the thing that needs to be done in contrast to what needs to be done for a real `noexcept` function? I doubt that there will be any difference apart from the code being inlined. The setup for the noxecept will be the same in either case. In your words: The "real" function likely will also need to provide its own "try" to enforce the noexcept. – Nobody moving away from SE Jan 30 '14 at 19:33
  • @JamesKanze: Why would a try block have to be generated if the function were not declared noexcept? And what is the basis for your claim that the cost of a try block is zero? On Win32, my understanding is that both MSVC and gcc generate runtime code to manipulate data structures for objects requiring destruction. (Not all common exception implementations use tables of exception regions.) – KnowItAllWannabe Jan 30 '14 at 20:12
  • @Nobody: The inline function may call non-`nothrow` functions that are either separately compiled (so compilers can't see what they do) or that the function author knows don't throw with the values being passed. In that case, there would be no need for the would-be `noexcept` function to have its own `try` block. – KnowItAllWannabe Jan 30 '14 at 20:19
  • 1
    @KnowItAllWannabe For the first, you still have to call destructors on any local variables. And for the second: the cost of a try block is zero, because the compiler generates exactly the same code if you have a try block, or if you don't. (At least, any decent compiler does. The standard doesn't forbid generating extra code, but there's no reason to.) – James Kanze Jan 31 '14 at 11:24

2 Answers2

11

let's assume that noexcept does make meaningful code-generation optimizations possible

OK

Assuming such functions are actually inlined, this would seem to require that compilers generate the equivalent of a try block around the code resulting from the inline function at the call site, because if an exception arises in that region

Not necessarily, because it might be that the compiler can look at the function body and see that it cannot possibly throw anything. Therefore the nominal exception-handling can be elided.

If the function is "fully" inlined (that is, if the inlined code contains no function calls) then I would expect that the compiler can fairly commonly make this determination -- but not for example in a case where there's a call to vector::push_back() and the writer of the function knows that sufficient space has been reserved but the compiler doesn't.

Be aware also that in a good implementation a try block might not actually require any code at all to be executed in the case where nothing is thrown.

With that assumption, does it make sense to declare inline functions noexcept?

Yes, in order to get whatever the assumed optimizations are of noexcept.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • Your comment about compilers optimizing `try` blocks away by examining their contents is independent of this question. Compilers can always perform that kind of optimization. – KnowItAllWannabe Jan 30 '14 at 17:41
  • @KnowItAllWannabe: I think it is relevant to the question. If the try block has zero performance cost then the try block becomes *irrelevant* to the consideration of whether it's a good idea to use `noexcept`, and as far as I can tell the question mostly goes away. Of course even if no code is executed it might not have exactly zero cost, since the exception-handling could bloat the binary. But then, inlining can bloat the binary too and nevertheless generally it increases performance. – Steve Jessop Jan 30 '14 at 17:46
  • 1
    A better example of an impossible exception would be `bad_alloc` on `vector::push_back()` after a sufficient `vector::reserve()`. Virtually impossible to prove for a compiler, quite trivial for a human. – MSalters Jan 31 '14 at 08:42
3

It is worth noting that there was an interesting discussion in circles of power about nothrow-related issues. I highly recommend reading these:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3227.html

http://www.stroustrup.com/N3202-noexcept.pdf

Apparently, quite influential people are interested in adding some sort of automatic nothrow deduction to C++.

After some pondering I've changed my position to almost opposite, see below

Consider this:

  • when you call a function that has noexcept on declaration -- you benefit from this (no need to deal with unwindability, etc)

  • when compiler compiles a function that has noexcept on definion -- (unless compiler can prove that function is indeed nothrow) performance suffers (now compiler needs to ensure that no exception can escape this function). You are asking it to enforce no-exceptions promise

I.e. noexcept both hurts you and benefits you. Which is not the case if function is inlined! When it is inlined -- there is no benefit from noexcept on declaration whatsoever (declaration and definition become one thing)... That is unless you are actually want compiler to enforce this for safety sake. And by safety I mean you'd rather terminate than produce wrong result.

It should be obvious now -- there is no point declaring inlined functions noexcept (keep in mind that not every inline function is gonna get inlined).

Lets have a look at different categories of functions which don't throw (you just know they don't):

  1. non-inlined, compiler can prove it doesn't throw -- noexcept won't hurt function body (compiler will simply ignore specification) and call sites will benefit from this promise

  2. non-inlined, compiler can't prove it doesn't throw -- noexcept will hurt function body, but benefit call sites (hard to tell what is more beneficial)

  3. inlined, compiler can prove it doesn't throw -- noexcept serves no purpose

  4. inlined, compiler can't prove it doesn't throw -- noexcept will hurt call site

As you see, nothrow is simply badly designed language feature. It only works if you want to enforce no-exception promise. There is no way to use it correctly -- it can give you "safety", but not performance.

noexcept keyword ended up being used both as promise (on declaration) and enforcement(on definition) -- not a perfect approach, I think (lol, second stab at exception specs and we still didn't get it right).

So, what to do?

  1. declare your behavior (alas, language has nothing to help you here)! E.g.:

    void push_back(int k);   // throws only if there is no unused memory
    
  2. don't put noexcept on inline functions (unless it is unlikely to be inlined, e.g. very large)

  3. for non-inline functions (or function that is unlikely to be inlined) -- make a call. The larger function gets the smaller noexcept's negative effect becomes (comparatively) -- at some point it probably makes sense specifying it for callers' benefit

  4. use noexcept on move constructor and move assignment operator (and destructor?). It could affects them negatively, but if you don't -- certain library functions (std::swap, some container operations) won't take the most efficient path (or won't provide the best exception guarantee). Basically any place that uses noexcept operator on your function (as of now) will force you to use noexcept specifier.

  5. use noexcept if you don't trust calls your function makes and rather die than have it behave unexpectedly

  6. pure virtual functions -- more often than not you don't trust people implementing these interfaces. Often it makes sense buying insurance (by specifying noexcept)

Well, how else noexcept could be designed?

  1. I'd use two different keywords -- one for declaring a promise and another for enforcing it. E.g. noexcept and force_noexcept. In fact, enforcement isn't really required -- it can be done with try/catch + terminate() (yes, it will unwind the stack, but who cares if it is followed by std::terminate()?)

  2. I'd force compiler to analyze all calls in given function to determine if it can throw. If it does and a noexcept promise was made -- compiler error will be emitted

  3. For code that can throw, but you know it doesn't there should be a way to assure compiler that it is ok. Smth like this:

    vector<int> v;
    v.reserve(1);
    ...
    nothrow {       // memory pre-allocated, this won't throw
        v.push_back(10);
    }
    

    if promise is broken (i.e. someone changed vector code and now it provides other guarantees) -- undefined behavior.

Disclaimer: this approach could be too impractical, who knows...

C.M.
  • 3,071
  • 1
  • 14
  • 33