20.9 — Exception specifications and noexcept

(h/t to reader Koe for providing the first draft of this lesson!)

In C++, all functions are classified as either non-throwing (do not throw exceptions) or potentially throwing (may throw an exception).

Consider the following function declaration:

Looking at a typical function declaration, it is not possible to determine whether a function might throw an exception or not. While comments may help enumerate whether a function throws exceptions or not (and if so, what kind of exceptions), documentation can grow stale and there is no compiler enforcement for comments.

Exception specifications are a language mechanism that was originally designed to document what kind of exceptions a function might throw as part of a function specification. While most of the exception specifications have now been deprecated or removed, one useful exception specification was added as a replacement, which we’ll cover in this lesson.

The noexcept specifier

The noexcept specifier defines a function as non-throwing. To define a function as non-throwing, we can use the noexcept specifier in the function declaration, placed to the right of the function parameter list:

Note that noexcept doesn’t actually prevent the function from throwing exceptions or calling other functions that are potentially throwing. Rather, when an exception is thrown, if an exception exits a noexcept function, std::terminate will be called. And note that if std::terminate is called from inside a noexcept function, stack unwinding may or may not occur (depending on implementation and optimizations), which means your objects may or may not be destructed properly prior to termination.

Much like functions that differ only in their return values can not be overloaded, functions differing only in their exception specification can not be overloaded.

The noexcept specifier with a Boolean parameter

The noexcept specifier has an optional Boolean parameter. noexcept(true) is equivalent to noexcept, meaning the function is non-throwing. noexcept(false) means the function is potentially throwing. These parameters are typically only used in template functions, so that a template function can be dynamically created as non-throwing or potentially throwing based on some parameterized value.

Which functions are non-throwing and potentially-throwing

Functions that are non-throwing by default:

  • default constructors
  • copy constructors
  • move constructors
  • destructors
  • copy assignment operators
  • move assignment operators

However, if any of the listed functions call (explicitly or implicitly) another function which is potentially throwing, then the listed function will be treated as potentially throwing as well. For example, if a class has a data member with a potentially throwing constructor, then the class’s constructors will be treated as potentially throwing as well. As another example, if a copy assignment operator calls a potentially throwing assignment operator, then the copy assignment will be potentially throwing as well.

Best practice

If you want any of the above listed functions to be non-throwing, explicitly tag them as noexcept (even though they are defaulted that way), to ensure they don’t inadvertently become potentially throwing.

The following are potentially throwing by default:

  • Normal functions
  • User-defined constructors
  • Some operators, such as new

The noexcept operator

The noexcept operator can be used inside functions. It takes an expression as an argument, and returns true or false if the compiler thinks it will throw an exception or not. The noexcept operator is checked statically at compile-time, and doesn’t actually evaluate the input expression.

The noexcept operator can be used to conditionally execute code depending on whether it is potentially throwing or not. This is required to fulfill certain exception safety guarantees, which we’ll talk about in the next section.

Exception safety guarantees

An exception safety guarantee is a contractual guideline about how functions or classes will behave in the event an exception occurs. There are four levels of exception safety:

  • No guarantee -- There are no guarantees about what will happen if an exception is thrown (e.g. a class may be left in an unusable state)
  • Basic guarantee -- If an exception is thrown, no memory will be leaked and the object is still usable, but the program may be left in a modified state.
  • Strong guarantee -- If an exception is thrown, no memory will be leaked and the program state will not be changed. This means the function must either completely succeed or have no side effects if it fails. This is easy if the failure happens before anything is modified in the first place, but can also be achieved by rolling back any changes so the program is returned to the pre-failure state.
  • No throw / No fail -- The function will always succeed (no-fail) or fail without throwing an exception (no-throw).

Let’s look at the no-throw/no-fail guarantees is more detail:

The no-throw guarantee: if a function fails, then it won’t throw an exception. Instead, it will return an error code or ignore the problem. No-throw guarantees are required during stack unwinding when an exception is already being handled; for example, all destructors should have a no-throw guarantee (as should any functions those destructors call). Examples of code that should be no-throw:

  • destructors and memory deallocation/cleanup functions
  • functions that higher-level no-throw functions need to call

The no-fail guarantee: a function will always succeed in what it tries to do (and thus never has a need to throw an exception, thus, no-fail is a slightly stronger form of no-throw). Examples of code that should be no-fail:

  • move constructors and move assignment (move semantics, covered in chapter M)
  • swap functions
  • clear/erase/reset functions on containers
  • operations on std::unique_ptr (also covered in chapter M)
  • functions that higher-level no-fail functions need to call

When to use noexcept

Just because your code doesn’t explicitly throw any exceptions doesn’t mean you should start sprinkling noexcept around your code. By default, most functions are potentially throwing, so if your function calls other functions, there is a good chance it calls a function that is potentially throwing, and thus is potentially throwing too.

The standard library’s policy is to use noexcept only on functions that must not throw or fail. Functions that are potentially throwing but do not actually throw exceptions (due to implementation) typically are not marked as noexcept.

Best practice

Use the noexcept specifier in specific cases where you want to express a no-fail or no-throw guarantee.

Best practice

If you are uncertain whether a function should have a no-fail/no-throw guarantee, error on the side of caution and do not mark it with noexcept. Reversing a decision to use noexcept violates an interface commitment to the user about the behavior of the function. Making guarantees stronger by retroactively adding noexcept is considered safe.

Why it’s useful to mark functions as non-throwing

There are a few good reasons to mark functions a non-throwing:

  • Non-throwing functions can be safely called from functions that are not exception-safe, such as destructors
  • Functions that are noexcept can enable the compiler to perform some optimizations that would not otherwise be available. Because a noexcept function cannot throw an exception, the compiler doesn’t have to worry about keeping the runtime stack in an unwindable state, which can allow it to produce faster code.
  • There are also a few cases where knowing a function is noexcept allows us to produce more efficient implementations in our own code: the standard library containers (such as std::vector) are noexcept aware and will use the noexcept operator to determine whether to use move semantics (faster) or copy semantics (slower) in some places (we cover move semantics in chapter M).

Dynamic exception specifications

Optional reading

Before C++11, and until C++17, dynamic exception specifications were used in place of noexcept. The dynamic exception specifications syntax uses the throw keyword to list which exception types a function might directly or indirectly throw:

Due to factors such as incomplete compiler implementations, some incompatibility with template functions, common misunderstandings about how they worked, and the fact that the standard library mostly didn’t use them, the dynamic exception specifications were deprecated in C++11 and removed from the language in C++17 and C++20. See this paper for more context.

20.x -- Chapter 20 comprehensive quiz
20.8 -- Exception dangers and downsides

9 comments to 20.9 — Exception specifications and noexcept

  • Berrie

    Some feedback on this section:
    1) The site index does not mention noexcept.
    2) In several cases there is referred to chapter 15. I think these references should be to chapter M.

  • Fan

    In the last part, the examples of no-fail functions include :
    * clear/erase/reset functions on containers
    * operations on std::unique_ptr (also covered in chapter 15)
    but those may call the destructors of the corresponding objects which is only no-throw but not no-fail. Is there a problem here?

  • sulfur93

    I was like dude 2 days ago there was no 14.9. Thank you for keeping us updated guys and koe!

  • koe

    <3 it looks awesome!

    One line lost from draft: "Note: If a function has been declared with noexcept, all other declarations of that function must also have noexcept. If it was declared with noexcept(false), other declarations can neglect it." IIRC this rule was initially hacked in, then in C++17 was better integrated by making 'noexcept' part of a function's type alongside the return type (explaining why it can't be used to overload).

    • nascardriver


      Declarations having to be identical is true for most specifiers/qualifiers. An exact repetition of the declaration is what is done naturally when writing the definition, so saying that declarations have to be identical isn't necessary. We're not trying to cover all aspects of features, rather we try to keep only the most important information to make it easier to remember. If we try to cram too much information into a lesson, the readers won't be able to memorize everything.

      Thanks for your contribution!

  • goiu

    Damn, i was approaching the end of the tutorial and hence there is a new lesson D: (lol just joking, i'm so glad that a website bornt in 2007 (more or less lol) is still active!)
    One question: i don't understand the first best-practice:
    "..., to ensure they don’t inadvertently inherit a potentially throwing value"
    what does mean "inherit a potentially throwing value"? for how i understood that paragraph, i think that this simply means that the functions listed don't call another functions potentially throwing, including the constructor of their members, in the way described in the paragraph above, that is that if an exception is throwed, then the std::terminate will be called... is that right?

    • koe

      Which defaults are created under which situations is not 'robust and clear'. For example, a default move constructor is only created when there is a default copy constructor. If there is a user-defined copy constructor then there will be no default move constructor, and all moves will go through the copy constructor (unless a user-defined move constructor is provided). Marking defaults as noexcept ensures they will be used, without thinking too hard about which defaults are implicitly generated.

      A more clear rule might be:
      If you want any of the above listed functions to be non-throwing, explicitly tag them as noexcept (even though they are defaulted that way), to ensure they are treated as non-throwing no matter what, even if they happen to inherit a potentially throwing function (e.g. a constructor for an object with a potentially throwing member variable or base class constructor).

    • nascardriver

      I slightly updated this best practice to avoid confusion with inheritance. What this best practice means is, if you want one of the listed functions to be `noexcept`, but you don't mark it `noexcept`, it might be potentially throwing (ie. noexcept(false)) because of a function that you're calling from the listed function.

      `std::terminate` is only called if an exception tries to exit a `noexcept` function. If one of the listed functions is potentially throwing (Because you didn't add `noexcept` and called a potentially throwing function), it can throw exceptions like any other function.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]