noexcept — what for?

In this post I would like to share my observation on where using noexcept really adds value. It is less often than what one might expect, and it does not have that much to do with throwing or not throwing exceptions. The conclusion surprises me a bit, and I hesitate to present it because it is counter to the advice I hear from people I consider authorities on the subject.

Semantics of noexcept

What does it mean to the program that a function is declared as noexcept? Forget what the intent of the programmer is, or could be. Just answer a ‘technical’ question: what does it change in the program from the perspective of run-time behaviour, observable results and the type system?

Two most important things:

  1. It turns an exception throw into a call to std::terminate().
  2. One can make a compile-time query if the function has been declared as noexcept.

This can be illustrated with the following mean “Hello World” program:

// DON'T DO IT AT HOME!

void printMessage()
{
  cout << "Hello World!" << endl;
  exit(0);
}

void execute() noexcept
{
  if (noexcept(execute())) throw "Ha!";
}

int main()
{
  set_terminate(&printMessage);
  execute();
}

This is the opposite of any good programming practice, but it illustrates my point well: the intent of putting a feature into the language is one thing and the formal semantics of the feature is another.

There is one other effect of noexcept, although less interesting for this post. Calling std::terminate() in the situation above does not require performing stack unwinding, or the stack can be only partially unwound.

Why do you need to know?

Is this an important information for you if a given function may throw or not? If so, why? Some possible answers include the following:

  1. Because if it throws and I do not catch it, std::terminate will be called and I do not want this to happen.
  2. Because I need this function to provide no-throw exception safety guarantee.
  3. Because this may allow certain compiler optimizations.

Did I miss anything?

If your motivation is (1), noexcept buys you nothing. While you know that no exception will be propagated out, std::terminate() is called for you even sooner!

If your motivation is (2), noexcept will also not help you much. Suppose you detect that the function is not declared as noexcept, what do you do? Don’t use it? It may still not throw exceptions. A function can throw nothing and still be declared noexcept(false). This is the case for the std::swap specialization for STL containers: following the recommendation from N3248 it is guaranteed not to throw, but is declared noexcept(false). For this reason, the example I gave in my other post with function nofail_swap is wrong. It doesn’t take into account that swap on STL containers is no-fail. You cannot check the no-fail or no-throw guarantee with a compile-time expression, because in some cases it is just announced informally in the documentation.

Also, if a function is declared noexcept it can’t throw, but it can call std::terminate. Do you consider this behaviour suitable for a component that is supposed to be “exception safe”?

If your motivation is (3), then there is a chance that you will gain some performance, but not as much as you think (if any). Compiler can perform certain no-throw optimizations even without your help (especially for inline functions). And annotating anything with noexcept comes with certain costs: the risk of calling std::terminate and making function declarations more obscure. C++ allows you to annotate functions wit many information, but when you try to use all of it, function declarations can get really long. Consider these:

constexpr inline int const& getX() const noexcept;

or:

virtual inline int const& getY() const noexcept override [[carries_dependency]];

So unless you are determined you want to have performance squeezed of these noexcept declarations and you have measured that they actually speed the program up rather than slow it down, I would consider it a premature optimization.

So, what is it for?

Given this negative attitude to noexcept, can it be considered useful at all? Yes. The noexcept feature was introduced very late into C++11 to address one particular issue with move semantics. It has been described here by Douglas Gregor and David Abrahams. In short, for certain functions, like vector<T>::push_back using move constructors/assignments of T instead of copy constructors/assignments can dramatically increase performance. However, if this move constructor/assignment can potentially throw, push_back would loose the strong exception safety guarantee. In order to take an advantage of the move operations (where possible) and at the same time keep the strong guarantee, there needs to be a way to determine if a given move operation is potentially throwing or not, and use it or alternatively fall back to good old copying. This is exactly what function std::move_if_noexcept does, but it needs T’s operations to be marked as noexcept where appropriate.

But when is it appropriate, given that a move operation declared as noexcept may call std::terminate? (Note that this is the case for std::thread.) The answer is: you (the author of class T) decide. If you want the extra performance from vector<T>::push_back at the risk of loosing the strong guarantee, just add it. You are not risking much if you are ‘sure’ your move operations do not throw. The other part of the guarantee (that std::terminate is called upon violating noexcept) is not even necessary here. Only that you are able to declare your choice and that function overload resolution is able to read your choice. In fact, initially the violation of noexcept was proposed to be an undefined behavior.

So, in other words, the only convincing (for me) usage of noexcept is to declare to function overload resolution mechanism your resolution of the trade-off between run-time performance and exception safety guarantees. Performance obtained this way can be orders of magnitude greater than noexcept-based code generation optimizations. Now, I am talking only about annotating move constructors and move assignments this way. This technique could be applied to any function, but so far, I haven’t seen it used for anything else but move operations.

Note that in the description above no throwing of exceptions is mentioned.

It is no-fail, not no-throw

Now, consider the following somewhat unusual definition of a move assignment.

int Tool::operator=(Tool&& rhs)
{
  if (ResourceHandle tmp = getSentinelResource()) {
    dispose(this->resourceHandle);
    this->resourceHandle = rhs.resourceHandle;
    rhs.resourceHandle = tmp;
    return EXIT_SUCCESS;
  }
  else {
    return EXIT_FAILURE;
  }
}

Nothing in this code throws exceptions. We use a 3rd party library which does not throw exceptions. We do not throw exceptions ourselves: instead we signal failure using the return value.

Now, imagine we are holding our class Tool in an std::vector. Do we want to speed up the operations by using moves instead of copies? Since move assignment can fail, the same argument from N2855 still holds, the strong exception safety would be compromised. We do not want that, so we need to signal to the overload resolution mechanism that for our class Tool we want the copies to be used rather than moves. Therefore our move assignment must not be declared as noexcept even though we guarantee that we do not throw exceptions! This is because the information that noexcept really is intended to convey is that the function never fails; not that it never throws! We can see above that a function can fail but still not throw, but it still qualifies for noexcept(false). Perhaps the keyword should have been called nofail. The never-fail guarantee cannot be checked by the compiler (much like any other failure-safety guarantee), therefore the only thing we can do is to declare it.

This is part of a more general observation, that what we are interested in is really failure safety in program components rather than exception safety. No matter if you use exceptions, error return values, errno or whatever else, the reasoning about basic (no leak, invariant preserved), strong (commit or rollback) and never-fail guarantee should still hold.

More eager termination

There is one other usage of noexcept (tied to exceptions for a change) that I can think of, but never needed in practice. This is to call std::terminate more often in hope of finding some failure safety bugs sooner in the application development process.

To illustrate what I mean, consider destructors. If an exception is thrown from a destructor during stack unwinding std::terminate is called. If this is your design decision that your destructors should throw, calling std::terminate is an acceptable and desired consequence. On the other hand, if you are trying to apply the best practice widely accepted in C++ community that destructors should not throw, calling std::terminate in this case is an indication that you inadvertently did throw from a destructor. In that case it is a bug, but one that occurs very seldom. Suppose that you throw from a destructor 1 in 1000 times. Even if you do, it is again 1 in a 1000 times that your exception is called due to stack unwinding. So the probability of calling std::terminate due to double-exception situation is 1/1000000 (assuming the two events are independent), and is likely not to occur during testing, and only in production. On the other hand, if the destructor is marked as noexcept, std::terminate is going to be called whenever an exception is thrown from destructor, during stack unwinding or normal scope exit. So the probability of the throw from destructor being revealed is 1/1000 instead of 1/000000, and it becomes more likely that we will see it when the program is still in testing stage. And when it happens during testing, the provision that the stack need not be unwound proves very beneficial, because std::terminate can be called directly at the point of the throw and we can precisely identify the culprit.

For this reason destructors are implicitly declared noexcept in C++11. Is this a convincing use case? Answer yourself.

About these ads
This entry was posted in programming and tagged , , , , . Bookmark the permalink.

20 Responses to noexcept — what for?

  1. George says:

    I may be mistaken, but I thought the noexcept operator did not evaluate the expression, and therefore you wouldn’t get a terminte() called for you sooner.

    “noexcept buys you nothing. While you know that no exception will be propagated out, std::terminate() is called for you even sooner!”

    • I am not sure I entirely understand your concern but perhaps the following helps.

      There are two different usages of keyword noexcept:

      1. An operator that takes an (unevaluated) expression and says if it only involves functions declared as noexcept.
      2. A part of function declaration that (a) makes the above operator work and (b) changes an exception throw into std::terminate at run time.

      So the usage (1) works as you say: evaluates no expression at run-time: no termination. However the declaration (2) changes the behaviour of the program so that it can call std::terminate in places where it would otherwise propagate an exception. In a pervert way it does guarantee that the function will not throw: but it offers something potentially worse instead.

  2. gnzlbg says:

    Completely agree. Thanks for writing this.

    Since Scott Meyers wrote his post about recommending “wider” usage of noexcept I’ve been having nightmares about infinite program execution paths leading to a std terminate call for no win in performance whatsoever.

  3. JDT says:

    Would the performance implications be more attractive if throwing out of a noexcept method had undefined behaviour; rather than defined behaviour?

    • I cannot answer this question with certainty. I have not studied any exception handling implementation in that detail. Based on the opinions of other people (e.g., noted in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3081.pdf), I believe that the provision that stack unwinding is allowed to be broken in case we break the guarantee makes it possible for compilers to generate as effective code (in the no-exception path) as if UB was allowed, at least for some implementations of stack unwinding mechanisms.

    • Alex says:

      No. All modern compilers use what is known as “zero cost” exception handling implementation. This means that not a single instruction of code related to exception handling is executed until an exception is thrown. There are however other costs to such implementations: executable size typically grows by 10-20%, once exception is thrown a lot of code is executed, etc.

      • “once exception is thrown a lot of code is executed” — and I believe this can be reduced due to noexcept

        • Alex says:

          Well, yes, calling std::terminate() is one way to avoid executing a lot of code.

        • “Well, yes, calling std::terminate() is one way to avoid executing a lot of code.” — fair point. I am desperately trying to find an example of performance gain. But it looks like I cannot find any.

        • Alex says:

          > I am desperately trying to find an example of performance gain.
          > But it looks like I cannot find any.

          Both speed and size compiler optimizations are possible for noexcept functions, but they are exactly the same optimizations which are possible for functions with empty throw() specification.

        • What you gain with noexcept is the ability to apply these optimizations in more places because you can express a ‘conditional’ or ‘transitive’ noexcept guarantee:

          Wraper<T>::Wraper(Wrapper&&) 
          noexcept(noexcept(is_move_constructible<T>::value));
          
        • Alex says:

          Yes, it’s the only improvement of noexcept over empty throw() specification. But writing such code means taking big risk for tiny gain and I suspect this will not be used much outside of standard library.

  4. szborows says:

    “Because this may allow certain compiler optimizations.”
    What certain optimizations do you mean?
    AFAIK, after compilers convert source code into immediate representation (e.g. GIMPLE for GCC), they can easily remove try-catch boilerplate around pieces that never throw. Do you know what other optimizations are possible that wouldn’t be possible without `noexcept` keyword?

    • For one, with noexcept compilers can remove try-catch boilerplate even around pieces that do (illegally) throw.
      Also, I guess that in order to perform full program analysis you have to do it at link-time (and then it might be too expensive). noexcept allows optimizations during single translation unit processing.

      But I am not deep into compiler architecture.

  5. Martin Moene says:

    tiny typo in:
    …operations (where possible) and at the same keep the strong guarantee, there needs to be a…

    s/at the same keep/at the same time keep/

    thanks for the blog & cheers,
    Martin

  6. Matthew Fioravante says:

    noexcept kind of reminds me of the C restrict keyword. You can use it to optimize but if you get it wrong (templates, function pointers, virtual functions, any kind of user supplied dynamic dispatch that may throw) god help you.

    It seems to me that adding noexcept everywhere could be a great way to introduce bugs into your program for dubious or non-existent optimization benefits. A classical premature optimization.

    Thank you for writing this article. I believe your approach is superior to Scott Meyers and others who have been suggesting using noexcept more often. It should probably be restricted to its original intention, that is only if you need to have 2 code paths, one for the function throwing and another for it never throwing (move operations being the main example). I would hope that questions about how to use these new features get resolved before people start writing books and giving advice which later turns out to be not so good.

    The holy grail of compile time/run time exception specification and validation in the source code seems like a red herring. We tried once with throw() and now again with noexcept. Perhaps we should give up on this and just focus on how to write exception safe code using RAII. You cannot guarantee any exception specification unless your function has no form of dynamic (compile or runtime) dispatch.

  7. minorfs says:

    IMO using noexcept is like using const, most notably, its good practice to make ‘extensive’ use of it, but correctly applying it to a legacy code base is a pain, a whole lot of work, and (like in the early days of const correctness) puts severe limitations on the libraries you can use. Just like we don’t say we should not use const because libraries may ‘mutable’ themselves out of the const contract, it does equally not seem like a good idea to promote such a thing for what I would like to call noexcept correctness. The important thing about const correctnes in the light of ‘mutable’ is the same as the important thing about noexcept correctness in the light of throwing anyway: “stick to the bleeding contract!”

    • There is one difference though, between the two. In case of const, compiler support comes in two parts: (1) it makes use of your const annotations, but also (2) it immediately detects your inadvertent violations of const-ness. True, you can mutate when you are determined to do so, but you cannot do it by mistake. In case of noexcept you get compiler support only for the first part. This difference is significant enough — IMO — to say that noexcept-correctness cannot be recommended with the equal enthusiasm as const-correctness.

  8. It would have been nice to just prevent exceptions from being caught instead of throwing std::terminate(), so we can have a core-dump or the debuger-stop with a stack that goes as far as it can get, and with more state to be analyzed. That way we could just put noexcept everywhere and start opening every function one by one once we have modified them to throw the correct exceptions. This pass is not normally done at first-write, at least I don’t. First I write my functions as if every uncatched exceptions will behave like an assert, and then begin modifying them to throw appropriate ones. The problem with this approach is that I don’t really know if they’re not gonna be caught, I leave it to chance. That version of noexcept would have helped me a lot, knowing that at first every thrown exception will behave like an assert (which in practice the majority of them should), and then as a second “QA” pass begin the preparation for throwing and catching the appropriate exceptions.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s