Exception Specifications in C++ 2011

Exception Specifications in C++ 2011

By Dietmar Kühl

Overload, 19(103):13-17, June 2011


The new standard is almost finished. Dietmar Kühl looks at the new exception features.

This article discusses exception specifications in C++ 2011. The primary focus of this discussion is the new noexcept keyword and the related concepts. The reason for this strong focus is simply that exception specifications for anything else than the distinction between functions which may throw and functions which will not throw are a failed experiment. This will discussed at the end of this article. However, the specification that a function will not cause any exception is an important piece of information.

Motivation

It is just fun to watch a bunch of experts discuss something which is supposedly entirely under control, just to find shortly prior to shipping a product that just a tiny detail got missed. Taking advantage of move construction to optimize libraries is one such topic which got discussed for ages, always essentially under the assumption that move construction won’t throw any exceptions. Well, eventually it transpired that there are examples where moving an object might throw. This is the tiny detail which got missed. Once people looked more closely at the issue it turned out that this slight oversight actually turned into a tremendous monster, threatening to make use of move construction in the standard library impossible at all: the typical type currently in existence doesn’t have a move constructor. Instead, moving an object would actually turn into using its copy constructor and the assumption that copy constructors don’t throw exceptions is adventurous at best. The only positive aspect of this mess is that it actually was caught prior to shipping!

Let’s start with an example of the problem: assume we want to implement the class template std::vector<T> (ignoring the allocator because this particular issue doesn’t even need an allocator to land itself in a mess). To be more concrete, we want to implement this class’s reserve() method: this method increases the number of available elements before the internal memory needs to be rearranged. The particular aspect of its specification we are interested in is that this function leaves the std::vector<T> unchanged if an exception occurs. The C++2003 approach to implement the case where additional memory needs to be allocated ( reserve() is a bit more complex but we aren’t really interested in the other cases for this discussion) is to do the following:

  1. allocate enough memory to hold the requested number of elements
  2. copy the elements into the newly allocated memory
  3. set up std::vector<T> ’s data structure to use the new storage
  4. destroy the original sequence of elements
  5. deallocate the original memory.

Note that this actually only works correctly if neither the destructor of the element type T nor the deallocation function ever throw an exception. This is already a requirement for the type T in C++2003. This implementation provides the strong exception guarantee (i.e. the vector<T> stays unchanged if an exception is thrown during reserve() ):

  • If an exception is thrown during step 1 the object isn’t changed, yet.
  • If an exception is thrown during step 2 the original sequence isn’t changed, yet, either; for proper clean up the already copied elements need to be destroyed and the allocated memory needs to be released but this is doable quite easily as none of those clean-up operations may throw an exception.
  • After this, the operations are only manipulations of built-in types which don’t throw, destructor calls, and releasing the memory, none of which is allowed to throw an exception.

Of course, in C++2011 we can do better: we can move the objects rather than copying them! That is, we replace step 2 to just move the elements (at first sight it seems as if we could get rid of step 4, too, but even after moving the objects still exist and they need to be destroyed). The problem is that in general moving an object may throw! If this happens, we need to restore the original state of the sequence which would involve moving the objects back. Of course, if a move just threw, we have no guarantee that moving an object back doesn’t throw, too! That is, once a move threw, we can’t necessarily restore the original sequence. If we can’t restore the original sequence we can’t use std::move() (or whatever other approach we use to move objects) to implement std::vector<T>:reserve() .

This same logic essentially applies to many other places where moving an object would be beneficial: if the move operation may throw and thus cannot be undone safely it cannot be used in most places where the strong exception guarantee is given. As a result moving objects would be restricted to a few places in the standard C++ library. This is essentially an unacceptable prospect.

To prevent C++ standard melt-down people went ahead and constructed a solution for the problem which, unfortunately, is somewhat painful due to some of the choices made. At least, it seems to work: functions can be declared not to throw any exception using either the now deprecated empty throw specification – throw() – or the newly introduced noexcept specification. A noexcept specification can take a constant expression evaluating to bool making it conditional. And noexcept can be used as an operator, determining whether an expression can throw an exception at compile-time i.e. the noexcept operator is a constant expression.

The key idea is that the implementer of a function knows in many cases that the function won’t throw an exception. For example, often move construction of a resource-owning class just moves pointers around and sets the original pointers to null . None of these operations throws. That is, in many cases moving the objects is viable but it is necessary to tell the system that the move constructor doesn’t throw. Operations capable of possibly moving objects rather than copying them then use a noexcept operator to detect whether the move is guaranteed not to throw.

In principle, an empty throw specification in C++ 2003 pretty much says the same thing as a noexcept(true) specification or an empty throw specification in C++ 2011. In both cases an exception leaking out of the corresponding function causes the program to std::terminate() . However, there are a number of important details:

  1. If an exception is leaking from a function with a throw() specification (both in C++ 2003 and C++ 2011), the program is terminated following a call to std::unexpected() (in the general case unexpected() could change the exception into one which is expected but with an empty throw specification there is no exception which would be expected). However, before std::unexpected() is called the stack is unwound up to the function with the throw() specification. In the case of an exception escaping from a function declared noexcept(true) , stack unwinding may or may not happen as the system sees fit and std::terminate() is called directly without also calling std::unexpected() . This difference allows for some optimizations which are thought to remove any potential overhead from functions declared not to throw anything. The empty throw specification may have a performance impact even if no exception ever escapes the corresponding function.
  2. It isn’t possible to have a conditional empty throw specification but the noexcept(expr) specification takes an optional Boolean constant expression as argument, i.e. the noexcept specification can be conditional: if the passed expression expr evaluates to false a function can throw exceptions; otherwise, the function is not allowed to throw any exception. This is especially important for function templates: depending on the template parameters expressions may or may not throw exceptions. With a conditional specification it is possible to take advantage of this.
  3. In C++ 2003 it cannot be determined at compile time if a function is declared with an empty throw specification. This is changed in C++ 2011 where it can be determined whether a function is not allowed to throw any exception i.e. whether it has an empty throw specification or a noexcept(true) specification: the expression noexcept(expr) (note that noexcept is used as an operator here) yields a Boolean compile-time constant indicating if all operations in the expression expr are declared to be noexcept(true) or throw() .

Originally, the noexcept specification was intended to be much stronger: it would be a compile-time error trying to use any operation which isn’t noexcept(true) . This would have meant that calling any function without a noexcept(true) specification would have to be wrapped into a try/catch block where one of the catch blocks would have been required to catch any exception using ( ... ). This very strong requirement was dropped because it was considered to hamper transition to noexcept(true) use. The use of a throw() specification is discouraged because it may incur some run-time overhead even if no exception is thrown. In C++ 2003 this is the only way to specify that no exception will be thrown. Thus, existing code is lacking proper exception specifications and it is often not an option to change some of the code. Adding noexcept(true) specifications to code based on functions lacking proper exception specifications would either fail to compile or require try/catch wrappers if it were an error to call a function which isn’t noexcept(true) .

The flipside of the coin is that now it becomes necessary to protect functions using noexcept(true) specifications against changes in underlying libraries who are specified to not throw exceptions originally: while this guarantee exists when the noexcept(true) specification was added nothing prevents removal of the corresponding exception specifications. This silent change would potentially cause programs to abort because an optimization was safe at some point but was broken because some lower-level code got changed. Although changing a noexcept(true) specification to a noexcept(false) specification effectively amounts to an interface change, it is a change which isn’t detected by the compiler. Having to use either conditional noexcept specifications or static_assert() s to prevent code from silently breaking, at least if it uses user-defined functions, is quite painful. Static analysis may yield warnings about potentially throwing operations and I’d think this would be a valuable tool given that any accidentally thrown exception terminates the program. I would have preferred an error or, if this is deemed not acceptable as a default, some way to opt into stronger checks (e.g. a noexcept block which causes an error if potentially throwing operations are used within).

noexcept-specification

Functions can be declared to never throw any exception by using a noexcept specification following the function declaration, e.g.:

  void f() noexcept;

This declaration could alternatively have used an empty throw specification:

  void f() throw();

These two declarations of f() are compatible: both declarations can appear in a program and with respect to the declaration their meaning is identical: both declare that f() can’t throw an exception. Incompatible exception specifications, i.e. exception specifications allowing/disallowing different exceptions are not permitted. However, the definition of the function determines how the program proceeds if an exception escapes the function with the exception specification: if the function definition uses a noexcept specification, std::terminate() is called immediately; if the function definition uses a dynamic exception specification, i.e. throw() , then std::unexpected() is called which will ultimately call std::terminate() , too. The key difference between both approaches is that before calling std::unexpected() the stack is unwound while there is no such guarantee when std::terminate() is called immediately.

The noexcept specification can take a Boolean constant expression as argument which determines whether the function should be noexcept . For example:

  void g() noexcept(true);
  // same as noexcept without argument
  void h() noexcept(false);
  template <typename T>
     void m() noexcept(noexcept(T());

The first two examples are rather simple: g() is declared to never throw any exception and h() is declared to possibly throw an exception. Whether the function m() is allowed to throw an exception depends on the template parameter T : using a noexcept operator (see below) this specification determines whether the default constructor or the destructor of T might throw an exception. If they don’t, the function m() is specified not to throw any exception, either. Otherwise, i.e. if the default constructor or the destructor of T might throw an exception, m() might throw an exception as well. Note that the expression T() constructs a temporary object using the default constructor and destroys the object again. Thus, both the default constructor and the destructor are considered!

With explicit exception specifications the situation is rather straightforward: if there is an exception specification, a function just gets the corresponding specification. It gets more interesting if there is no exception specification. In this case a function is normally allowed to throw exceptions. Most of the time this is what is desired but it would also cause all destructors without exception specification to be considered throwing although it has long been recommended that destructors should be non-throwing. In addition, it is very desirable that destructors can be detected as being non-throwing. Therefore, destructors get special treatment: if the destructor has no explicit exception specification, it gets the same exception specification an implicitly generated destructor would get. So, let’s see what happens there.

There are a number of implicitly generated functions for which it would be desirable to have noexcept(true) specifications where possible. This applies to the default constructor, copy constructor, move constructor, destructor, copy assignment, and move assignment, commonly called the special functions . When one of these special functions is implicitly generated, it gets an exception specification which allows all exceptions of all operations called by the implicitly generated special function. For example, the implicitly generated copy constructor copies all bases and all members. The corresponding exception specification will consist of a union of all exceptions specified by the copy constructors of the bases and the members. This exception specification is possibly equivalent to noexcept(false) , i.e. all exceptions are allowed: this is the case if at least one of the base or member copy constructors allows all exceptions. If none of the copy constructors of the members or bases can throw any exception, i.e. they are all declared to be noexcept(true) , then the exception specification of the generated copy constructor will also be noexcept(true) .

Now, for destructors these rules apply even if the destructor is explicit but has no exception specification. That is, if your destructor does throw an exception but none of the destructors of the bases or members throws exceptions, you will have to declare explicitly that the destructor might throw an exception (of course, you should consider whether your destructor really needs to throw as well). To allow an explicitly implemented destructor to throw an exception it needs to either have a throw(exceptions) specifier or it needs to get a noexcept(false) specification. Somehow this latter declaration reminds me of ‘Yes! We have no bananas’!

Note that this is a silent change: a destructor without an exception specification which throws an exception gets an exception specification which may very well disallow any exceptions! I couldn’t verify this behaviour on any of the compilers I have currently available, however. This may be due to this being a relatively recent change (it made its first appearance in N3225 ). Listing 1 is the example program I used to test.

typedef int the;
int const up(42);

struct the_oracle
{
    ~the_oracle() { throw up; }
};

int main()
{
    try { the_oracle(); }
    catch (the answer) {}
}
			
Listing 1

The destructor of the_oracle will acquire a throw() or a noexcept(true) exception specification (which one is unclear in the standard; the difference is whether the stack is partially unwound or not, respectively, before the program is terminated). Either will cause the program to be terminated instead of returning normally from main() . Yes, I know that it is bad practice to throw from a destructor but this doesn’t mean that there is no code out there which benefits from having this change pointed out.

noexcept operator

To determine whether an expression might, according to its exception specifications, throw an exception, the noexcept operator can be used:

  template <typename T>
  void f() {
    if (noexcept(T()))
      ...

In this example the noexcept operator tests if the default constructor and the destructor for the template argument T are specified not to throw any exception: if the expression T() were executed it would use the default constructor to create a temporary which would then be destroyed using the destructor. Thus, both operations have to be declared not to throw an exception for the noexcept operator to yield true . The result of the noexcept operator is a constant expression and the expression passed to the noexcept operator is not evaluated i.e. if the expression has side effects, these won’t happen (just the same as sizeof which is also a constant expression with the argument not being evaluated).

In general, the argument to the noexcept operator can be an arbitrary expression. The noexcept operator yields false if there is any potentially evaluated expression or subexpression which may throw an exception, i.e. if there is any expression or subexpression which is not specified to be noexcept(true) or throw() . Note that this includes any implicit operations needed by the expression like implicit conversions or destructors. Of course, any dynamic_cast<>() on references, throw expression, or typeid expression would also cause the noexcept operator to yield false . However, if all expressions or subexpressions are specified to be noexcept(true) or throw() the noexcept operator yields true as well.

The noexcept operator is the key using non-throwing operations in optimizations for any sort of templatized component. Let’s get back to the original example of implementing std::vector<T>::reserve() : this function could determine whether objects can be moved without ever throwing an exception using an expression like this:

  noexcept(T(std::declval<T>())))

The function template std::declval() is declared to have a return type matching its template argument, possibly turned into an r-value, and is specified not to throw an exception itself. It is only declared and has no definition, however:

  template <typename T>
  typename std::add_rvalue_reference<T>::
     type declval() noexcept;

With this, the expression above just tests whether it is possible to both construct an object of type T from a movable T object and to destroy an object of type T without throwing any exception. Although the noexcept operators can be used, it is worth pointing out that the header <type_traits> defines a number of type traits which do this test more directly. Likewise, there is a function declared in the header <utility> which takes care of all the needs for moving vs. copying of objects: std::move_if_noexcept() is similar to std::move() but the result type is only an r-value reference if the move constructor of the argument type is specified to be noexcept(true) .

Although the noexcept operator is an important facility in the C++ tool box, it is similar to the sizeof operator: the noexcept operator is probably rarely used explicitly. However, type traits and auxiliary functions effectively based on this operator are likely to show up in many places: it is beneficial to expose that operations won’t throw any exception in many places and libraries trying to be as efficient as possible will make use of this knowledge.

User and standard library use

In general, it seems as if noexcept(true) specifications should be freely used wherever it is known that a function can’t throw an exception. Given that an incorrect noexcept(true) specification might terminate the program it should not be applied carelessly, however. In practice this probably means that many functions which could be specified to be noexcept(true) won’t get this specification. Hopefully, the danger of wrongly using noexcept(true) will be mitigated by compilers and/or static analysers which could warn about dangerous noexcept(true) specifications or suggest functions which could safely be made noexcept(true) . Given that this is a brand-new feature I don’t expect any such tool to be around already. Also, it is probably worth verifying that noexcept(true) specifications have indeed no adverse run-time effect.

From a semantic view the noexcept(true) specification should have no impact. It is worth noting, however, that declaring a move constructor to be noexcept(true) will cause several standard library algorithms to use the move constructor rather than the copy constructor. Obviously, these constructors should be implemented in a way which makes it viable to use a move constructor instead of a copy constructor. For implicitly generated constructors this is already the case, assuming that any user-defined constructors used to deal with subobjects have this property, too. In practice, specifying a move constructor to be noexcept(true) should just make the program faster.

Although noexcept(true) can be useful for many functions its use for now, at least, mainly affects algorithms in the standard library. These mainly care about some of the special functions and the swap() function: all of the operations which might be involved in moving objects, i.e. the move constructor, the move assignment, the destructor, and the swap() function should, whenever possible, be written not to throw any exceptions. In most cases these operations are probably simple pointer operations which can’t throw an exception. Any of these operations which indeed never throws an exception should also be declared to be noexept(true) . This is the area where the biggest performance gains from moving over copying are expected.

In the standard library there are certain functions which are required to be noexcept(true) . Some functions have to be specified conditionally noexcept(true) . For the vast majority of functions in the standard library the standard makes no requirement that the function has to be specified noexcept(true) . However, the library implementer is free to specify any non-virtual function for which it is known that no exception is thrown noexcept(true) . It is expected that the library implementers make use of this freedom. The freedom to strengthen the exception specifications isn’t given for virtual functions: since the overriding functions have to apply the same exception specification or stronger than the base class version, allowing the library implementer to strengthen the exception specification for any virtual function would cause incompatible implementations.

Use in the library specification

The original approach for noexcept specifications in the standard C++ library specification was to make any function which has a clause saying ‘throws nothing’ noexcept(true) . This seems like a sensible approach but actually it is not! The reason for this is not necessarily obvious: a lot of the functions have preconditions and if these preconditions are met, the function indeed doesn’t throw any exception. However, what happens when a precondition is not met? The answer is that this causes undefined behaviour, i.e. anything, including throwing an exception, could happen. For example, a checking version of the standard library might detect that a precondition isn’t met and signal this by throwing an exception. If the corresponding function were required to be noexcept(true) this safe implementation of the standard library would cause a rather unhelpful termination. Thus, the rules when to require noexcept(true) got revised to cater for this.

To better describe the rules used to determine whether a function is made noexcept(true) or not, it is helpful to define two terms:

  • A function without any precondition is said to have a wide contract . Obviously, for member functions the object on which such a function is called actually does have the precondition that the object exists. An example of a function with a wide contract is std::vector<T>::size() : whenever there is corresponding vector object around, this function can be called
  • A function which has some precondition is said to have a narrow contract . std::vector<T>::pop_back() is an example of a function with a narrow contract because it requires that there is at least one element in the vector. However, its specification still says that it throws nothing.

The rules for when noexcept(true) is to be applied for standard library functions which are specified to throw nothing depend on whether the respective function has a wide or a narrow contract: since functions with wide contracts cannot be abused, i.e. there is no precondition which can be violated, it is safe to make them noexcept(true) if they can’t throw an exception. However, any function which has a narrow contract, even though it may be specified to not throw any exception when it is correctly used, can throw an exception if the precondition is violated. That is, no function with a narrow contract is required to be noexcept(true) .

An implementation which doesn’t do any checking of the precondition is allowed to strengthen the exception specification, however. This means that the corresponding function may actually get different throw specifications in different build configurations: in a safe mode where the preconditions are checked and violations are signaled via an exception, the function is noexcept(false) while in a release build where the preconditions are not checked it may become noexcept(true) .

Exception specifications in general

While the distinction between throwing functions and non-throwing functions is rather useful, especially if this property of a function can be detected, more general exception specifications are not. Essentially, the idea to declare what kind of exceptions a function can throw is a failed experiment. Well, if someone had thought hard enough about this it wouldn’t even have been necessary to run an experiment! The intention of exceptions is to relieve business logic from forwarding error information. The goal is to allow handling of exceptional errors at an appropriate level without interfering with the business logic. Obviously, adding a specification of what errors might happen in a specific function is nothing else than burdening the business logic with information on error forwarding – something directly defeating the goal of exceptions. Yes, it isn’t in the body of the code and to some extent separated but it is still there and typically has nothing to do with the business logic other than exposing some more or less random implementation details.

I’d think this is already bad enough but it actually becomes worse! Most interesting functions are generic in some form, be it that they are virtual, take a template argument or a function pointer, or depend on other objects which are somehow customizable. How on earth can an author of such a function even dream of possibly anticipating what kind of exception will be thrown in the setup in which I am calling it? Any exception specification just becomes an unnecessary road-block which would require me to disguise my carefully crafted exception hierarchy as some amorphous piece of junk which later needs to be recovered carefully and inspected at every potential catch-site.

With C++ 2011 the class std::exception_ptr and the related means to get hold of such a beast give me the opportunity to disguise my exceptions in a reasonably generic way (e.g. I could construct some sort of chain of disguised exceptions) which wasn’t available earlier. Note, however, that this is not the intention of this class! Instead, it is intended to marshal exceptions unchanged from one thread of execution to another one. Yes, it could be used to create chains of disguised exceptions so that we can play nicely with exception specifications. Of course, having to remember that the exception which was caught needs to be demarshaled and investigated for its actual content makes the processing of exceptions harder rather than easier. Also, I have actually lost the claimed benefit of exception specifications because I actually need to handle errors which are explicitly not specified. The use of a tool which makes life harder in return for no benefit whatsoever is hardly advisable.

Just to be explicit for those who wonder: this assessment is actually not specific to C++! It applies to all languages which are misled to participate in this experiment. Maybe the actual experiment is to see how many programming language communities can be made to believe that a specification of which exceptions a function might throw is somehow a good thing? Obviously, this isn’t related to the noexcept discussion but I needed to get this off my chest anyway.

Conclusions

With C++2011 it becomes possible to conditionally specify functions to not throw any exceptions. In addition, it becomes possible to detect whether any given expression might throw an exception. The positive end of this is that a lot of operations may become faster by taking advantage of the fact that there are no exceptions. At the negative end, getting the exceptions specification wrong will std::terminate() the program and the user has to manually add the exception specifications to all but the implicitly generated special functions which deduce their exception specification from the operations they implicitly call. Hopefully, there will soon be tools which warn about noexcept(true) specifications in functions which actually might throw or suggest adding noexcept(true) for functions which are known not to throw but don’t have the corresponding exception specification, yet.

Acknowledgement and references

Thanks to Daniel Krügler, Jens Maurer, and Nicolai Josuttis for their very constructive feedback on several drafts of this document.

This article is based on several documents used during the C++ standardization:

N2855 – Rvalue References and Exception Safety (Douglas Gregor, David Abrahams)

N3225 – Working Draft, Standard for Programming Language C++ (Pete Becker) as of 2010-11

N3291 – Working Draft, Standard for Programming Language C++ (Pete Becker) as of 2011-04

N3279 – Conservative use of noexcept in the Library (Alisdair Meredith, John Lakos)






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.