Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

The Debate on noexcept, Part I

Last updated Jan 1, 2003.

At the Pittsburgh meeting in March 2010, the ISO C++ committee added the new keyword noexcept to the FCD. The precise semantics of noexcept is still a subject for debates. Here I will outline the reasons for adding noexcept, contrast it with traditional exceptions specifications that were just deprecated, and introduce the main points of controversy.

Exception Specifications

There's been a long standing consensus that the C++98 exception specifications are no good. There are several problems with them, most of which have been discussed in length previously. I'll briefly summarize them again.

Dynamic checking. Exception specifications are checked at runtime, not at compile-time. Consequently, programmers calling a function with a certain exception specification can't be sure that the exception specification will be honored at runtime. Furthermore, an implementation must generate additional code to perform the runtime checking of exceptions. That leads to inefficient code and hampers certain optimizations.

Generic Code. It's difficult to write accurate exception specifications in generic code because the designer can't tell what types of exceptions may be thrown from operations on template arguments.

The vast experience of the last two decades shows that in practice, only two forms of exceptions specifications are useful:

  • The lack of an overt exception specification, which designates a function that can throw any type of exception:
  • int func(); //might throw any exception
  • A function that never throws. Such a function can be indicated by a throw() specification:
  • int add(int, int) nothrow; //never throws

In practice, most functions that never throw don't include an overt throw() specification because exception specifications have fallen from grace, and because a throw() specification incurs performance overhead (I'll discuss the overhead shortly).

These facts are known to the majority of C++ programmers. It's no wonder then that the committee decided in its last meeting to deprecate exception specifications.

Note: The traditional exception specifications in the form

int f() throw(x,y...);

are now referred to as "dynamic exception specifications", to make them distinct from the newly added keyword noexcept, which serves as a compile-time exception specification.

Why Not Stop At Deprecation?

If the committee had settled for deprecating dynamic exception specifications, very few eyebrows would have been raised. However, some members felt that a mechanism for indicating a function that never throws was necessary (especially in move operations) as an optimization cue to the compiler. At this point you're probably wondering: "isn't that what the throw() exception specification does anyway?" Yes, but not exactly. Indeed throw() indicates that a function doesn't throw. However, suppose that a function declared throw() violates its exception specification and throws an exception:

void f() (const char * text) throw();
{
std::string s(text);
}

Although f() doesn't have an overt throw statement, its throw() specification is misleading because the constructor of std::string might throw. What should happen in that case? The C++98 standard requires that the implementation shall unwind the stack of the offending function (invoke destructors of local objects, flush I/O streams etc) and then invoke the Standard Library function std::unexpected(). unexpected() may try (at least in theory) to recover from the exception, which is why an implementation must ensure proper unwinding of the stack. That "proper unwinding of the stack" is what inhibits optimizations of functions declared throw().

In contrast, noexcept implies no stack unwinding. Furthermore, whereas a dynamic exception specification is checked at runtime, the noexcept keyword is checked at compile-time, when possible. Although the compiler can't detect every violation of noexcept, it will issue a diagnostic if it can determine that a function declared noexcept might throw, as in the following example:

void g() noexcept
{
 throw Myexception("disaster!"); //C++0x compilation error/warning
}

I believe that at this stage of the C++0x standardization process, the best approach would have been to stop at the deprecation of dynamic exception specifications. We know they're bad and we don't use them anyway. It's too late now to invent a new exception specification mechanism just days before the approval of the FCD. However, that's exactly what happened in Pittsburgh -- the new keyword noexcept was added to the FCD, causing controversy (the minutes from that meeting are available here).

How Does noexcept Differ from throw()?

The proposal doesn't provide all the details with respect to the semantic differences between throw() and noexcept. Generally speaking, the latter is (supposedly) a compile-time specification, as opposed to the former which is checked at runtime. Practically, that means two things:

A compiler may be able to apply certain optimizations to a function declared noexcept. Those optimizations are disabled by default because a function that's not declared noexcept might throw any exception type.

Additionally, the proposal requires that in the event of a noexcept violation, the implementation shall call terminate() (instead of unexpected()) More specifically, the implementation shall not invoke local objects' destructors if a noexcept violation has occurred.

The Debate on terminate()

The requirement to invoke terminate() in the event of a noexcept violation started a debate: Why not state that noexcept violations causes undefined behavior? After all, a noexcept violation shouldn't occur, and if it does, it shouldn't be different from other instances of bad programming practice, say deleting the same pointer twice, buffer overflows etc. Recall that undefined behavior means that anything can happen, including what appears to be normal execution flow.

Another practical argument against calling terminate() was raised. A number of domain areas, such as embedded programming, require terminate() to never be called, insisting that an application should continue to run forever. Therefore, it may be best to let each implementation decide how to handle noexcept violations instead of prescribing that terminate() shall be called.

However, other members strongly opposed to the idea of allowing a program to continue executing past the noexcept call because the results of continuing the program's execution without unwinding the stack might be disastrous -- leaked locks, corrupted files etc. Furthermore, programmers writing code for a catch clause would not be able to distinguish a "valid exception" from an exception that has violated noexcept and therefore couldn't tell how to handle an exception at all.

In the next part of this series I will discuss the arguments in favor of, and against invoking terminate() in depth, and try to predict what will come out of this proposal.