Rvalue References and Exception Safety

Authors: Douglas Gregor, David Abrahams
Contact: [email protected], [email protected]
Organization: Apple, BoostPro Computing
Date: 2009-03-23
Number: N2855=09-0045

Table of Contents

Introduction

This paper describes a problem with the move construction idiom that compromises the exception safety guarantees made by the Standard Library. In particular, well-formed C++03 programs, when compiled with the C++0x Standard Library, can no longer rely on the strong exception safety guarantee provided by certain standard library container operations. This change silently breaks existing user code. In this paper, we characterize the problem itself and outline a solution that extends the language and modifies the library.

Review of Exception Safety Guarantees in the Library

Within the library, we characterize the behavior of a function with respect to exceptions based on the guarantees that the implementation must provide if an exception is thrown. These guarantees describe the state of the program once a thrown exception has unwound the stack past the point of that function. We recognize three levels of exception safety guarantees for a given library function:

Basic exception guarantee
The invariants of the component are preserved, and no resources are leaked.
Strong exception guarantee
The operation has either completed successfully or thrown an exception, leaving the program state exactly as it was before the operation started.
No-throw guarantee
The operation will not throw an exception.

The Standard Library provides at least the basic exception guarantee throughout, which has not changed with the introduction of rvalue references. However, some functions of the library provide the strong exception guarantee, such as push_back (23.1.1p10).

Review of Move Semantics Idioms

The most common "move constructor" signature for a type T is T(T&&), but every move constructor:
  1. Can be called with a single non-const rvalue argument of type T
  2. Has a non-const reference parameter to which that rvalue is bound
  3. Transfers resources from that rvalue into the newly-constructed object
Although move construction modifies its argument by transferring resources away from it, move construction from a real rvalue is still a "logically non-mutating" operation. Since the move constructor holds the only reference to that rvalue, any modifications are invisible to the rest of the program. These invisible move constructions can take effect automatically whenever an rvalue may need to be copied, for example when an rvalue argument is passed by value. However, move construction can also be explicitly requested for lvalue arguments. The standard idiom is T(std::move(x)), where x is an lvalue of type T. The std::move function template simply casts its lvalue argument to the corresponding rvalue type:
template <class T>
T&& move(T& x) { return static_cast<T&&>(x); }
The T(std::move(x)) idiom is only a move request because in general, T may not have a move constructor, in which case an available copy constructor will be used instead. Such an explicit move request can be used anytime a function knows that a particular value is no longer needed. The simplest example is in std::swap:
template <class T>
void swap(T& x, T& y)
{
   T tmp(std::move(x));  // 1
   x = std::move(y);     // 2
   y = std::move(tmp);   // 3
}
In this case, it is safe to move from x in line 1 because we are about to assign into x in line 2. Since copying x is also an acceptable (though sometimes suboptimal) way to transfer its value into tmp, this code still works when T has a copy constructor but no move constructor. The act of constructing a new T from an explicitly-generated T&& bound to an lvalue is known in this paper as "the move operation," whether it actually ends up moving or falls back to invoking a copy constructor.

The Problem

The problem addressed by this paper is three-fold. First, under C++0x, some existing types such as pair<string,legacy_type> automatically acquire a throwing move constructor. Second, currently-legal operations such as insertion into a container of those pairs will invoke undefined behavior, because the C++0x Standard Library bans throwing move constructors in those operations. Third, operations such as vector::push_back, that are currently required to provide the strong exception guarantee, only provide the basic guarantee when T's move constructor can throw.

The Problem With Throwing Move Constructors

As an example, we consider vector's push_back operation, e.g.,

vec.push_back(x);

In the call to push_back, if the size of the vector is the same as its capacity, we will have to allocate more storage for the vector. In this case, we first allocate more storage and then "move" the contents from the old storage into the new storage. Finally, we copy the new element into the new storage and, if everything has succeeded, free the old storage. The reallocation routine looks something like this:

T* reallocate(T *old_ptr, size_t old_capacity) {
  // #1: allocate new storage
  T* new_ptr = (T*)new char[sizeof(T) * old_capacity * 2];

  // #2: try to move the elements to the new storage
  unsigned i = 0;
  try {
    // #2a: construct each element in the new storage from the corresponding
    // element in the old storage, treating the old elements as rvalues
    for (; i < old_capacity; ++i)
      new (new_ptr + i) T(std::move(old_ptr[i])); // "move" operation
  } catch (...) {
    // #2b: destroy the copies and deallocate the new storage
    for (unsigned v = 0; v < i; ++v)
      new_ptr[v]->~T();
    delete[]((char*)new_ptr);
    throw;
  }

  // #3: free the old storage
  for (i = 0; i < old_capacity; ++i)
    old_ptr[i]->~T();
  delete[]((char*)old_ptr);
  return new_ptr;
}

For this discussion we are interested in section #2, which handles the movement of values from the old storage to the new storage. The use of std::move treats the elements in the old storage as rvalues, enabling a move constructor (if available) or falling back to a copy constructor (if no move constructor is available) with the same syntax.

Consider reallocation of the vector when the type stored in the vector provides only a copy constructor (and no move constructor), as shown below. Here, we copy elements from the old storage (top) to the new storage (bottom).

abcd efgh
abcd                           

While copying the fifth element (e) the copy constructor throws an exception. At this point, we can still recover, since the old storage still contains the original (unmodified) data. Thus, the recovery code (section #2b) destroys the elements in the new storage and then frees the new storage, providing the strong exception safety guarantee.

When the type stored in the vector provides a move constructor, each of the values is moved from the old storage into the new storage, potentially mutating the values in the old storage. The notion is shown below, half-way through the reallocation:

???? efgh
abcd                           

When the element's move constructor cannot throw, the initialization of the new storage is guaranteed to succeed, since no operations after the initial memory allocation can throw.

However, if the element's move constructor can throw an exception, that exception can occur after the vector's state has been altered (say, while moving the value e), and there's no reliable way to recover the vector's original state, since any attempt to move the previously-moved elements back into the vector's storage could also throw. Hence, the best we can do is maintain the basic guarantee, where no resources are leaked but the first four elements in the vector have indeterminate values.

A Model of Strong Exception Safety

Stepping back from this specific instance, we can formulate a simple model for achieving the strong exception-safety guarantee. In this model, we take the set of operations that we need to perform in our routine and partition them into two sets: those operations that perform nonreversible modifications to existing data and those operations that can throw exceptions. Providing strong exception safety means placing any operations that can throw exceptions (memory allocation, copying, etc.) before any operations that perform nonreversible modifications to existing data (destroying an object, freeing memory).

Reconsidering vector reallocation in terms of this model, we see that, if we ignore throwing move constructors, the implementation of reallocate performs all of its possibly-throwing routines up front: we allocate memory, then copy (which may throw) or move (which won't throw), then we complete the operation. Either way, at some point within the routine we have committed to only using operations that can no longer throw, such as deallocating memory or destroying already-constructed objects.

The problem with a throwing move constructor is that it fits into both partitions. It can throw exceptions (obviously) and it is also a non-reversible modification, because (1) moving is permitted to transfer resources and (2) there is no non-throwing operation to reverse the transfer of resources.

Based on this model, prohibiting the use of types that have throwing move constructors appears to solve the problem. It does help, but we'll need to go further than that to prevent the generation of throwing move constructors in standard library class templates.

Throwing Move Constructors in the Standard Library

So, how easy is it to violate the requirement that move constructors not throw exceptions? It turns out to be effortless. In fact existing, well-formed C++03 programs will violate this requirement when compiled with the C++0x Standard Library because the standard library itself creates throwing move constructors. As an example, consider a simple Matrix type that stores its values on the heap:

class Matrix {
  double *data;
  unsigned rows, cols;

public:
  Matrix(const Matrix& other) : rows(other.rows), cols(other.cols) {
    data = new double [rows * cols];
    // copy data...
  }
};

The Matrix type has a copy constructor that can throw an exception, but it has no move constructors. A vector of Matrix values is certainly well-formed and its push_back provides the strong exception safety guarantee. This is true both in C++03 and in C++0x.

Next, we compose a std::string with a Matrix using std::pair:

typedef std::pair<std::string, Matrix> NamedMatrix;

Consider std::pair's move constructor, which will look something like this (simplified!):

template<typename T, typename U>
struct pair {
  pair(pair&& other)
    : first(std::move(other.first)), second(std::move(other.second)) { }

  T first;
  U second;
};

Here, the pair's first data member is a std::string, which has a non-throwing move constructor that modifies its source value. The pair's second data member is a Matrix, which has a throwing copy constructor but no move constructor. When we compose these two types, we end up with a type—std::pair<std::string, Matrix>—that merges their behaviors. This pair's move constructor performs a non-reversible modification on the first member of the pair (moving the resources of the std::string) and then performs a potentially-throwing copy construction on the second member of the pair (copying the Matrix). Thus, we have composed two well-behaved types, one from the library and one from user code, into a type that violates the prohibition on throwing move constructors. Moreover, this problem affects valid C++03 code, which will silently invoke undefined behavior when compiled with a C++0x Standard Library

Proposed Solution

Given the prohibition on throwing move constructors, pair should only declare a move constructor when it is guaranteed that the underlying move operations for the types it aggregates are both non-throwing. Using concept syntax, one might imagine that such a constructor would be written as:

  requires NothrowMoveConstructible<T> && NothrowMoveConstructible<U>
    pair(pair&∓ other)
      : first(std::move(other.first)), second(std::move(other.second)) { }

In this case, pair will only provide a move constructor when that move constructor is guaranteed to be non-throwing. Therefore, std::pair<std::string, Matrix> will not provide a move constructor, and reallocating a vector of these pairs will use the copy constructor, maintaining the strong exception safety guarantee. Naturally, pair is not the only type whose move constructor is affected: any standard library and user type that aggregates other values, including tuples and containers, will need similarly-constrained move constructors.

At present, there is no satisfactory way to write the NothrowMoveConstructible concept. Using C++0x as currently specified, we could try to write the new concept as follows:

concept NothrowMoveConstructible<typename T, typename U = T> {
  requires RvalueOf<U> && Constructible<T, RvalueOf<U>::type>;
}

The important aspect of this concept is that it is not an auto concept, so clients are required to "opt in" by explicitly stating, via a concept map, that they provide a non-throwing move constructor. The library would provide concept maps for its own types, e.g.

concept_map NothrowMoveConstructible<string> { }

Some of these concept maps will, naturally, be conditional on their inputs. For example, pair's concept map can be expressed as follows:

template<typename T1, typename T2, typename U, typename V>
  requires NothrowMoveConstructible<T1, U> && NothrowMoveConstructible<T2, V>
  concept_map NothrowMoveConstructible<pair<T1, T2>, pair<U, V>> { }

If diligently applied throughout the library and user code, NothrowMoveConstructible permits the safe use of move semantics within the library, retaining the strong exception safety guarantee.

The danger with a library-only solution is that it is far too easy for users of the language to accidentally write a move constructor that can throw exceptions (see std::pair), and a single class or class template that makes such a mistake compromises the exception safety guarantees of the library. The concepts system cannot protect the user from such a mistake, because there is no way to statically determine whether a function can throw exceptions. Even if concepts could prevent such an error in the library, non-templated and unconstrained templates would still be susceptible to this class of errors. To address these problems, we propose to introduce language facilities that allow the non-throwing guarantee to be declared for functions, statically enforced by the compiler, and queried by concepts.

The noexcept Specifier

We propose the addition of a new declaration specifier, noexcept, that indicates that the function it applies to does not throw any exceptions. The noexcept specifier can only be applied to function declarators, e.g.,

noexcept int printf(const char* format, ...); // okay: printf does not throw exceptions.
noexcept int (*funcptr)(const char*, ...); // okay: pointer to a function that does not throw exceptions
noexcept int x; // error: not a function declarator

The noexcept specifier differs from an empty exception specification (spelled throw()) in two important ways. First, a noexcept function is ill-formed if it (or any function it calls) may throw an exception, e.g.,

noexcept int foo(int);
int bar(int);
noexcept void wibble(int x, int y) {
  x = foo(x); // okay: foo can not throw any exceptions
  y = bar(y); // error: bar() could throw an exception

  try {
    y = bar(y); 
  } catch (...) {
    y = 0;
  } // okay: all exceptions that could be thrown have been captured
}

Second, the presence or absence of the noexcept specifier is part of a function type. Thus, the types of the function pointers fp1 and fp2, shown below, are distinct:

noexcept int (*fp1)(int); // okay: pointer to a function that does not throw exceptions
int (*fp2)(int); // okay: pointer to a function that may throw exceptions

There is an implicit conversion from pointers and references to noexcept pointers to their potentially-throwing equivalents. For example:

noexcept int f(int);
int g(int);

noexcept int (*fp1)(int) = &f; // okay: exact match
int (*fp2)(int) = &g; // okay: exact match
int (*fp3)(int) = &f; // okay: conversion from pointer to noexcept function to a pointer to a non-noexcept function
noexcept int (*fp4)(int) = &g; // error: no conversion from a pointer to a throwing function type to a pointer to a non-throwing function type

In short, noexcept provides the behavior that many users expect from throw(). That exception specifications are not statically checked is a constant source of confusion, especially for programmers who have used the similar (statically-checked) facilities in Java. Moreover, exception specifications have a poorly-defined role in the C++ type system, because they can only be used in very limited ways.

Note: functions cannot be overloaded based on noexcept alone, and noexcept is not part of a function's signature. For example, the following code is ill-formed:

noexcept void f() { } // #1
void f() { } // #2: redefinition of #1

noexcept In Concepts

The noexcept specifier can be used on associated functions within concepts, allowing one to detect whether a particular operation is guaranteed not to throw exceptions. For example:

auto concept NothrowMoveConstructible<typename T, typename U = T> {
  requires RvalueOf<U>;
  noexcept T::T(RvalueOf<U>::type);
}

This new, noexcept formulation of the NothrowMoveConstructible concept has two benefits over the previous formulation. First, since the noexcept property is statically checked, there is no potential for users to accidentally claim that no exceptions will be thrown from their move constructors. Second, since we have strong static checking for the no-throw policy, we have made this an auto concept, so that users need not write concept maps for this low-level concept.

Move Constructors are Non-Throwing

Types with throwing move constructors are prohibited within the standard library, and we have not seen motivating use cases for such a feature. On the other hand, failure to mark a non-throwing move constructor as noexcept means that users will miss out on the many optimization opportunities in the standard library that depend on rvalue references.

We therefore propose that all move constructors be implicitly noexcept. This way, users that implement move constructors can be certain that their move constructors (1) will be used by the standard library and (2) will meet the non-throwing requirements of the standard library.

Specifically, for a class X, any constructor whose first parameter is of type X&& and whose remaining parameters, if any, all have default arguments, is considered a move constructor. For a class tempate X, any constructor or constructor template whose first parameter is of type X<T1, T2, ..., TN> for any T1, T2, ..., TN and whose remaining parameters, if any, all have default arguments, is considered a move constructor or move constructor template. If a constructor or constructor template is a move constructor or move constructor template, it is implicitly declared as noexcept.

Destructors are Non-throwing

Nearly every operation that involves construction of an object also involves destruction of that object. Therefore, the vast majority of uses of a non-throwing move constructor will also require a non-throwing destructor. Non-throwing destructors are not new; in fact, the C++ Standard Library requires that destructors not throw exceptions, and it is very rare that destructors ever throw. For these reasons, we propose that all destructors be implicitly be declared noexcept and are, therefore, banned from throwing exceptions.

Unlike with move constructors, there are some use cases for destructors that throw exceptions. One such example is a return value that can't be ignored, e.g.,

class ImportantReturn {
  int value;
  bool eaten;

public:
  ImportantReturn(int v) : value(v), eaten(false) { }

  ~ImportantReturn() {
    if (!eaten) throw important_return_value_ignored(value);
  }

  operator int() const {
    eaten = true;
    return value;
  }
};

For these destructors (which are a very slim minority) we require a syntax to disable the implicit noexcept. We propose to use the syntax throw(...) to mean "this function can throw any exception." This syntax is already a Microsoft extension, and fits in well with existing syntax.

Note that implicitly making destructors noexcept will break existing code for classes like ImportantReturn. Users will need to add the throw(...) specification on these destructors to return them to their C++03 behavior. Given that few destructors need to throw exceptions---and that such classes cannot be used with the C++03 or C++0x standard libraries---we expect that the impact of this breaking change will be small.

The noexcept Block

Statically checking that the body of a function does not throw any exceptions can reject as ill-formed certain programs that are careful to avoid throwing exceptions. As a simple example, imagine a function sqrt(double x) that computes the square root of x. If given zero or a negative number, it will throw an std::out_of_range exception. Now, imagine a function that uses sqrt():

double sqrt(double); // may throw exceptions

noexcept void f(double &x) {
  if (x > 0) {
    x = sqrt(x); // ill-formed: sqrt(x) might throw an exception!
  }
}

This code is ill-formed, because the call to may throw exceptions. The user has properly guarded the call to sqrt by checking its preconditions within f, but the compiler is unable to intepret the program to prove that f will not throw. To address this problem, we propose the addition of a noexcept block, which will be used as follows:

double sqrt(double); // may throw exceptions

noexcept void f(double &x) {
  if (x > 0) {
    noexcept { x = sqrt(x); } // okay: if sqrt(x) throws, invokes undefined behavior
  }
}

The noexcept block states that no exceptions will be thrown by the code within its compound statement. An exception that escapes from a noexcept block results in undefined behavior.

Deprecate Exception Specifications

Exception specifications have not proven to be as useful as we had hoped. Their lack of static checking has limited their usability and confused users, and they provide few benefits for compilers. Moreover, exception specifications aren't useful in generic code, where functions need to know whether an exception can be thrown or not but don't know (or care) what kind of exceptions can be thrown. For these and other reasons, exception specifications are discouraged [1, 2, 3].

In fact, the noexcept specifier---along with the ability to detect whether an operation is noexcept via concepts---provides precisely the statically-checked exception specifications that are required in C++ code. We therefore propose to deprecate C++ exception specifications in C++0x: the community has already decided to avoid exception specifications and noexcept provides a superior alternative.

Library Changes

The introduction of noexcept and its use throughout the standard library will require significant changes in a few areas. The changes are summarized below.

Nothrow Moving and Copying Concepts

We propose the introduction of new concepts for non-throwing assignment and construction:

auto concept NothrowConstructible<typename T, typename... Args>
  : Constructible<T, Args...> {
  noexcept T::T(Args...);
}

auto concept NothrowMoveConstructible<typename T> : MoveConstructible<T> {
  requires RvalueOf<T>
        && NothrowConstructible<T, RvalueOf<T>::type>;
}

auto concept HasNothrowAssign<typename T, typename U> : HasAssign<T, U> {
  noexcept result_type T::operator=(U);
}

auto concept NothrowMoveAssignable<typename T> : MoveAssignable<T>, HasNothrowAssign<T, T&&> { }

In addition, we need Nothrow variants of the concepts used for scoped allocators:

concept NothrowConstructibleWithAllocator<class T, class Alloc, class... Args> {
  noexcept T::T(allocator_arg_t, Alloc, Args&&...);
}

auto concept NothrowAllocatableElement<class Alloc, class T, class... Args> : AllocatableElement<Alloc, T, Args...> {
  noexcept void Alloc::construct(T*, Args&&...);
}

Finally, we improve the existing NothrowDestructible concept to statically check the no-throw requirements:

auto concept NothrowDestructible<typename T> : HasDestructor<T> {
  noexcept T::~T();
}

Move Constructors and Move Assignment Operators

Throughout the library, each class template that aggregates other templates and has a move constructor will need to have its move constructor and move assignment operator be specified as noexcept and only be provided when the operations they use are known to be noexcept. We summarize the changes here, but have omitted the changes for some library components pending a full review of the library.

20.3.3 Pairs [pairs]

Two of pair's move constructors will be modified as follows:

template<class U, class V> 
  requires NothrowConstructible<T1, RvalueOf<U>::type> && NothrowConstructible<T2, RvalueOf<V>::type> 
  noexcept pair(pair<U, V>&& p);

template<class U, class V, Allocator Alloc>
  requires NothrowConstructibleWithAllocator<T1, Alloc, RvalueOf<U>::type> 
        && NothrowConstructibleWithAllocator<T2, Alloc, RvalueOf<V>::type> 
  noexcept pair(allocator_arg_t, const Alloc& a, pair<U, V>&& p);

Similarly, pair's move assignment operator will be modified as follows:

template<class U , class V> 
  requires HasNothrowAssign<T1, RvalueOf<U>::type> && HasNothrowAssign<T2, RvalueOf<V>::type> 
  noexcept pair& operator=(pair<U , V>&& p); 
20.5.2 Class template tuple [tuple.tuple]

Two of tuple's move constructors will be modified as follows:

template <class... UTypes> 
  requires NothrowConstructible<Types, RvalueOf<UTypes>::type>... 
  noexcept tuple(tuple<UTypes...>&&);

template <Allocator Alloc, class... UTypes> 
  requires NothrowConstructibleWithAllocator<Types, Alloc, RvalueOf<UTypes>::type>... 
  noexcept tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&&);

tuple's move assignment operator will be modified as follows:

template <class... UTypes> 
  requires HasNothrowAssign<Types, RvalueOf<UTypes>::type>... 
  noexcept tuple& operator=(tuple<UTypes...>&&); 
23.3.2 Class template deque [deque]

Modify two of deque's constructors as follows:

requires NothrowAllocatableElement<Alloc, T, T&&> && NothrowMoveConstructible<Alloc>
   noexcept deque(deque&&);
requires NothrowAllocatableElement<Alloc, T, T&&> && NothrowConstructible<Alloc, const Alloc&>
   noexcept deque(deque&&, const Alloc&);

Modify the deque move assignment operator as follows:

requires NothrowAllocatableElement<Alloc, T, T&&> && MoveAssignable<T>
      && NothrowMoveAssignable<Alloc>
  noexcept deque<T,Alloc>& operator=(deque<T,Alloc>&& x);

Each of the other standard containers and container adaptors will require similar modifications.

noexcept Annotations

Since a noexcept function can only call into other noexcept functions, much of the standard library itself will require noexcept annotations to make those parts of the library usable. Therefore, for any function that is currently specified as throw() we propose to replace the throw() with a noexcept specifier. Additionally, for any operation with a clause

we propose to remove this clause and instead introduce the noexcept specifier on the corresponding declaration.

These changes are likely to effect a significant portion of the Standard Library, including nearly all of the C standard library. In most cases, the changes merely enforce requirements that have already been present in the library.

Alternative Syntax

We have considered several alternative syntaxes for noexcept, and we catalog the most promising alternatives here:

The nothrow keyword
Why it works: nothrow would be our first choice for a keyword to describe a non-throwing function (or block). This term has been in use colloquially to describe non-throwing functions and is also used within the standard library. If the technical problems (described below) can be overcome, we would prefer to use nothrow rather than noexcept.
Why it doesn't: the standard library also has an object named nothrow, which is declared as:
extern const nothrow_t nothrow;
If nothrow were to become a keyword, this object could no longer be defined in the standard library and would have to be removed. We then would have to provide some special syntax allowing existing uses of this object to still work. For example:
using std::nothrow;
X* ptr = new (nothrow) X;
X* ptr2 = new (std::nothrow) X;
A nothrow attribute
Why it works: both functions and statements can be annotated with attributes, and since attributes do not require a keyword we can use the "nothrow" identifier. In addition, nothrow is an attribute already supported by the GNU compiler with a similar meaning.
Why it doesn't: attributes aren't meant to affect the type system, but it is crucial for the "non-throwing" property to be a part of a function's type. Plus, the existing GNU attribute, whose semantics don't exactly match the intended behavior of noexcept, may prove a hindrance.

Variants of throw specifications (!throw, throw(void), throw(not))
Why it works: we have already carved out a syntactic space for describing the exception behavior of functions, so it doesn't require major surgery to extend that syntax.
Why it doesn't: noexcept behaves very differently from existing exception specifications, so having too many syntactic similarities confuses the semantics of the two features in a way that is likely to cause problems for programmers.

Last modified: Mon Mar 23 09:12:03 PDT 2009