Error Handling

The noexcept Keyword

A function known never to throw an exception can be declared using the specifier noexcept. The declaration acts both as a promise to a human and information to the compiler when it compiles the code.

An ordinary, simple function, say, adding two integers, seems like a good candidate for such a declaration:

#include <iostream>

// Promise that exceptions do not escape this function
int add(int a, int b) noexcept
{
    return a + b;
}

int main()
{
    std::cout << add(3, 4) << '\n';
    return 0;
}

The add function is declared noexcept. Its body executes a single addition on two ints, and there is no way this can throw. The call in main therefore works as expected and the program prints 7. The specifier advertises the function's guarantee.

The promise is held true at run time: If a noexcept function throws an exception, the program terminates immediately.

Here is an example. The function fail is declared noexcept but contains an exception. When the throw statement is reached, the exception handling mechanism detects that the promise of noexcept has been broken.

#include <iostream>
#include <stdexcept>

void fail() noexcept
{
    // Signal the error by throwing an exception
    throw std::runtime_error{"oops"};
}

int main()
{
    try
    {
        fail();
    }
    catch (std::runtime_error e)
    {
        std::cout << e.what() << '\n';
    }
    return 0;
}

Instead of looking for a handler that matches the exception type, it immediately calls std::terminate, and the program aborts.

The catch block at the end of main is never executed. The particular text of the message varies from one compiler and implementation to another, but it always shows that control has not been returned to "normal" exception handling.

Member functions can use the noexcept specifier as well.

#include <iostream>

class Counter
{
    int value{};

public:
    void reset() noexcept
    {
        value = 0;
    }

    int get() const noexcept
    {
        return value;
    }
};

int main()
{
    Counter c;
    // Clear the selected bit to zero
    c.reset();
    std::cout << c.get() << '\n';
    return 0;
}

Both member functions reset and get are declared noexcept. Their implementation is very simple and only manipulates primitive types, so neither can throw an exception. When reset is called, the member will be zeroed and then printing the member via a get function will result in 0 being printed.

noexcept can also be used as an operator, asking if a given expression is going to throw at all. If that is never the case, then noexcept applied to an expression yields true; otherwise, it yields false. An expression given to noexcept must be a constant expression.

#include <iostream>

int add(int a, int b) noexcept
{
    return a + b;
}

int main()
{
    std::cout << noexcept(add(1, 2)) << '\n';
    std::cout << noexcept(1 / 0) << '\n';
    return 0;
}

The expression noexcept(add(1, 2)) checks if the call to the noexcept declared add function throws at all; which it does not. So, it returns 1. By contrast, an integer division by zero, as in noexcept(1 / 0), yields false, because division by zero in integer math causes a "crash," which is not the same thing as an exception and is not declared noexcept. Code passed to the noexcept operator is never evaluated; it simply exists to have its exception specification inspected at compile time.

The noexcept keyword may also be used with a condition. For example, noexcept(condition).

#include <iostream>
#include <type_traits>

template <typename T>
// Call this function with the provided arguments
T identity(T value) noexcept(std::is_nothrow_copy_constructible_v<T>)
{
    return value;
}

int main()
{
    std::cout << identity(42) << '\n';
    return 0;
}

The template parameter T is inspected by the trait std::is_nothrow_copy_constructible_v, which returns true when T may be copy constructed without throwing an exception, and false otherwise. For the int parameter passed in main, the trait will return true, and the resulting function will be declared noexcept. The program prints 42.

If the condition evaluates to false, then the function is treated as a regular throwing function; no special runtime check is performed by noexcept. Conditional exception specifications can be used for templated functions that need to provide a different exception guarantee depending on the types they are invoked with.

The noexcept keyword can then do double duty: It can promise not to throw and terminate the program if that promise is broken, and it can be used as an operator to query any expression for its promise, giving the programmer as well as the compiler more certainty about the exceptional behavior of C++ programs.

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.