Modern C++

Make Your Code Faster with noexcept

That noexcept keyword is tricky, but just know that if you use it, your coding world will spin faster.

The noexcept keyword is a recent addition, so when should you use it? Here's what the guideline says:

If your function may not throw, declare it noexcept.

My only issue with this guideline is the use of the word "may" because some people will interpret that as equivalent to "must not throw" or "is not allowed to throw" and others as equivalent to "might not throw." However, the Reason section of the guideline makes it clear that the first meaning is intended:

If an exception is not supposed to be thrown, the program cannot be assumed to cope with the error and should be terminated as soon as possible. Declaring a function noexcept helps optimizers by reducing the number of alternative execution paths. It also speeds up the exit after failure.

The keyword noexcept serves two purposes (like most of the language, really): It's an instruction to the compiler, and it's also helpful to humans who are reading the code. But the humans need to know that it has two different meanings:

  • This function will never throw an exception. It doesn't use new, call a library function that might use new, perform any arithmetic that might overflow or underflow, or in any other way cause any kind of exception to be thrown.
  • This function will never throw an exception that can be caught and recovered from. If anything it does causes any kind of exception to be thrown, it is pointless to unwind the stack and go through the catch process, because things are in such a terrible state that recovering will not be possible. Just terminate.

This guideline addresses the first meaning. If your function cannot throw, you should mark it as noexcept so that everyone knows that. There's no judgement call in this -- the keyword was invented for just this purpose. So if I have a very simple class like this one:

class Something
{
private:
  int x;
public:
  Something(int xx): x(xx) {}
  int getX();
  void reset(){ x = 0; }
}; 

I can mark every one of those member functions noexcept. Clearly, the act of assigning an integer value to an integer isn't going to throw an exception.

If, as in this example, you implement one of the functions in your .cpp file rather than inline in the class declaration, the keyword is part of the signature and you must include it in both places. Here's the class definition with noexcept added:

class Something
{
private:
  int x;
public:
  Something(int xx) noexcept : x(xx) {}
  int getX() noexcept;
  void reset() noexcept { x = 0; }
};

And here's the implementation:

#include "Something.h"

int Something::getX() noexcept 
{ 
  return x; 
}

If you omit the noexcept in the implementation, you'll get a compiler error. Mine says:

error C2382: 'Something::getX': redefinition; different exception specifications

Yours is likely to be similar. The key is realizing that noexcept is an exception specification.

To follow this guideline, you need to develop the ability to reason about the kinds of exceptions your code is likely to throw. Most C++ developers underestimate the opportunities for exceptions to be thrown. Developers in general are optimists, so we don't like to think about things like, "What if there is no memory for new to return to me?" Or "What if the hard drive is failing and the file I just created no longer exists?" And they are pretty rare. Some functions, like the simple ones in the Something class, are clearly not going to throw.

But real code, that calls other code, that calls library code you didn't write, is harder to reason about. The temptation is strong to say, "I can't be sure, so I'm not marking this noexcept." If you follow that temptation, you won't be wrong. Everything will work as it should.

However, if you're able to mark a function noexcept, you'll make your application faster in two entirely different ways. First, the compiler doesn't have to do a certain amount of setup -- essentially the infrastructure that enables stack unwinding, and teardown on the way into and out of your function -- if no exceptions will be propagating up from it. (You can argue that this isn't applicable for inline functions like those in my example, but most functions are in fact quite a bit longer than demo code.) The more often the function is called, the more important this is.

Second, the Standard Library is noexcept-aware and uses it to decide between copies, which are generally slow, and moves, which can be orders of magnitude faster, when doing common operations like making a vector bigger. (Why? If the push_back code is moving 10 "old" elements into the new, larger vector, when one of the moves throws an exception, you can't just propagate the exception and carry on as though the push_back never happened, because the "old" vector is full of moved-from elements that can't be recovered. So if the move for the elements of your vector might throw, push_back won't use move, it will use the slower copy.) This noexcept-awareness on the part of the Standard Library means you really want your moves to be noexcept. And if your moves use some other member functions of your class, you want those to be noexcept -- not because the compiler insists on it, but because your reasoning as a human is easier if you can just say, "This move operation calls update and reset and they're both marked noexcept, so I know I can mark it noexcept." The same applies to your swap function -- in fact, other guidelines suggest explicitly writing your own swap and marking it noexcept.

Your best habit is to mark functions noexcept the same way you mark them const -- as your first automatic habit. Remove the marker only when it's clear you cannot use it. Unlike const, though, you won't get a compiler error if you mark something noexcept that throws. This function is perfectly happy to compile:

int Something::getX() noexcept 
{ 
  if (x == 3)
    throw std::exception("I refuse to return 3");
  return x; 
}

If you were ever to construct a Something with a value of 3 and then call getX, the application would terminate when the exception is thrown. There would be no stack unwinding and no chance for a catch block in the calling code to deal with the situation. That might even be what you want. But if it isn't, then you shouldn't mark getX as noexcept. That's why the guideline doesn't address the general case of when to use noexcept and when not to -- there are decisions to be made application by application, function by function, for the general case. All it says is, if you know for sure it won't throw, help your compiler, help the Standard Library, and mark it noexcept. That's an easy rule to follow.

About the Author

Kate Gregory has been using C++ since before Microsoft had a C++ compiler, and has been paid to program since 1979. Kate runs a small consulting firm in rural Ontario, Canada, and provides mentoring and management consultant services, as well as writing code every week. She has spoken all over the world, written over a dozen books, and helped thousands of developers to be better at what they do. Kate is a Microsoft Regional Director, a Visual C++ MVP, an Imagine Cup judge and mentor, and an active contributor to StackOverflow and StackExchange sites. She develops courses for Pluralsight, primarily on C++ and Visual Studio. Since its founding in 2014 she is the Open Content Chair and speaker for CppCon, the definitive C++ conference.

comments powered by Disqus

Featured

Subscribe on YouTube