When to Use noexcept And When to Not

Table of Contents

In C++ 11, a new keyword noexcept is introduced. Being a replacement of deprecated throw(), what is noexcept good for? When should it be used and when should be avoided?

1 Intro to noexcept

noexcept is a function specifier. It is used to specify a function whether will throw exception or not. It can be used as following:

void foo() noexcept;             // a function specified as will never throw
void foo2() noexcept(true);      // same as foo
void bar();                      // a function might throw exception
void bar2() noexcept(false);     // same as bar

However, same as throw(), noexcept implies no compile-time check. What if a function specified with noexcept throws an exception?

Unlike throw(), if an exception is thrown out of a noexcept function, std::unexpected will not be called. Stack unwinding, during which destructor of stack allocated objects will be called, is not guaranteed. But std::terminated will be called immediately.

noexcept is to provide information for compiler, allow it to generate optimized code against non-throwing functions.

- Great, "noexcept" means more optimized code. Let's use it as much as we can!
- DON'T!

Exception-free code is one of the hardest thing to do right. If you don't believe me, check out this question 1 on StackOverflow. Even though the function is simple at first, marking it noexcept will make it difficult to change in the future, or break existing client code if changed.

Let's see an interesting example.

## noexcept and std::vector

As you might have known, a vector has its capacity. If it's full when push_back, it will allocate a bigger memory, copy (or move if C++ 11) all existing elements to the new trunk, then add the new element to the back.

copy-construct.png

Figure 1: Use copy constructor to expand a vector

But what if an exception is thrown out while allocating memory, or copying the element to the new trunk?

  • If exception is thrown during allocating memory, the vector is in its original state. It's fine just re-throw the exception and let user handle it.
  • If exception is thrown during copy existing elements, all copied elements will be destroyed by calling destructor, allocated trunk will be freed, and exception thrown out to be handle by user code. 2

    After destroy everything, the vector is back to the original state. Now it's safe to throw exception to let user handle it, without leaking any resource.

2 noexcept and move

Come to the era of C++ 11, we have a powerful weapon called move. It allows us to steal resources from unused objects. std::vector will use move when it needs to increase(or decrease) the capacity, as long as the move operation is noexcept.

Suppose an exception throws during the move, the previous trunk is not the same as before move happens: resources are stolen, leaving the vector in an broken state. User cannot handle the exception because everything is in an nondeterministic state.

move-construct.png

Figure 2: Use move constructor to expand a vector

That's why std::vector relies on move constructor to be noexcept.

This is a demostration how client code would rely on noexcept as an interface specification. If later the noexcept requirement is not met, any code previously depends on it will be broken.

3 noexcept is an interface specification

It's easy to add noexcept in the begining, but it will be difficult to modify the interface, or remove noexcept.

Just like other interface specifications, the principle of when and how to use noexcept should be carefully considered, but in general:

When define an interface, parameter type should be as specific as
possible, and return type should be as specific as possible, so future
modification can be made easily.   

The =noexcept= specification should also be as *generic* as possible,
i.e. =noexcept(false)=.

4 References

In the An Effective C++11/14 Sampler, Scott Meyers talked about noexcept with above example. It's worth watching.

The "why not noexcept" question on StackOverflow.

Footnotes:

2

What if an exception is thrown during destroying copied elements? std::terminated will be called to terminate the program.

HomeTop