18.1.4. The noexcept Exception Specification

It can be helpful both to users and to the compiler to know that a function will not throw any exceptions. Knowing that a function will not throw simplifies the task of writing code that calls that function. Moreover, if the compiler knows that no exceptions will be thrown, it can (sometimes) perform optimizations that must be suppressed if code might throw.

Image

Under the new standard, a function can specify that it does not throw exceptions by providing a noexcept specification. The keyword noexcept following the function parameter list indicates that the function won’t throw:

void recoup(int) noexcept;   // won't throw
void alloc(int);             // might throw

These declarations say that recoup will not throw any exceptions and that alloc might. We say that recoup has a nonthrowing specification.

The noexcept specifier must appear on all of the declarations and the corresponding definition of a function or on none of them. The specifier precedes a trailing return (§ 6.3.3, p. 229). We may also specify noexcept on the declaration and definition of a function pointer. It may not appear in a typedef or type alias. In a member function the noexcept specifier follows any const or reference qualifiers, and it precedes final, override, or = 0 on a virtual function.

Violating the Exception Specification

It is important to understand that the compiler does not check the noexcept specification at compile time. In fact, the compiler is not permitted to reject a function with a noexcept specifier merely because it contains a throw or calls a function that might throw (however, kind compilers will warn about such usages):

// this function will compile, even though it clearly violates its exception specification
void f() noexcept       // promises not to throw any exception
{
    throw exception();  // violates the exception specification
}

As a result, it is possible that a function that claims it will not throw will in fact throw. If a noexcept function does throw, terminate is called, thereby enforcing the promise not to throw at run time. It is unspecified whether the stack is unwound. As a result, noexcept should be used in two cases: if we are confident that the function won’t throw, and/or if we don’t know what we’d do to handle the error anyway.

Specifying that a function won’t throw effectively promises the callers of the nonthrowing function that they will never need to deal with exceptions. Either the function won’t throw, or the whole program will terminate; the caller escapes responsibility either way.


Image Warning

The compiler in general cannot, and does not, verify exception specifications at compile time.


Arguments to the noexcept Specification

The noexcept specifier takes an optional argument that must be convertible to bool: If the argument is true, then the function won’t throw; if the argument is false, then the function might throw:

void recoup(int) noexcept(true);  //  recoup won't throw
void alloc(int) noexcept(false);  //  alloc can throw

The noexcept Operator
Image

Arguments to the noexcept specifier are often composed using the noexcept operator. The noexcept operator is a unary operator that returns a bool rvalue constant expression that indicates whether a given expression might throw. Like sizeof4.9, p. 156), noexcept does not evaluate its operand.

For example, this expression yields true:

noexcept(recoup(i)) // true if calling recoup can't throw, false otherwise

because we declared recoup with a noexcept specifier. More generally,

noexcept(e)

is true if all the functions called by e have nonthrowing specifications and e itself does not contain a throw. Otherwise, noexcept(e) returns false.

We can use the noexcept operator to form an exception specifier as follows:

void f() noexcept(noexcept(g())); // f has same exception specifier as g

If the function g promises not to throw, then f also is nonthrowing. If g has no exception specifier, or has an exception specifier that allows exceptions, then f also might throw.


Image Note

noexcept has two meanings: It is an exception specifier when it follows a function’s parameter list, and it is an operator that is often used as the bool argument to a noexcept exception specifier.


Exception Specifications and Pointers, Virtuals, and Copy Control

Although the noexcept specifier is not part of a function’s type, whether a function has an exception specification affects the use of that function.

A pointer to function and the function to which that pointer points must have compatible specifications. That is, if we declare a pointer that has a nonthrowing exception specification, we can use that pointer only to point to similarly qualified functions. A pointer that specifies (explicitly or implicitly) that it might throw can point to any function, even if that function includes a promise not to throw:

// both recoup and pf1 promise not to throw
void (*pf1)(int) noexcept = recoup;

// ok: recoup won't throw; it doesn't matter that pf2 might
void (*pf2)(int) = recoup;

pf1 = alloc; // error: alloc might throw but pf1 said it wouldn't
pf2 = alloc; // ok: both pf2 and alloc might throw

If a virtual function includes a promise not to throw, the inherited virtuals must also promise not to throw. On the other hand, if the base allows exceptions, it is okay for the derived functions to be more restrictive and promise not to throw:

class Base {
public:
    virtual double f1(double) noexcept; // doesn't throw
    virtual int f2() noexcept(false);   // can throw
    virtual void f3();                  // can throw
};

class Derived : public Base {
public:
    double f1(double);        // error: Base::f1 promises not to throw
    int f2() noexcept(false); // ok: same specification as Base::f2
    void f3() noexcept;       // ok: Derived f3 is more restrictive
};

When the compiler synthesizes the copy-control members, it generates an exception specification for the synthesized member. If all the corresponding operation for all the members and base classes promise not to throw, then the synthesized member is noexcept. If any function invoked by the synthesized member can throw, then the synthesized member is noexcept(false). Moreover, if we do not provide an exception specification for a destructor that we do define, the compiler synthesizes one for us. The compiler generates the same specification as it would have generated had it synthesized the destructor for that class.


Exercises Section 18.1.4

Exercise 18.8: Review the classes you’ve written and add appropriate exception specifications to their constructors and destructors. If you think one of your destructors might throw, change the code so that it cannot throw.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.76.72