Alex Rivera | Logout

Why aren't there compiler-generated swap() methods in C++0x?

Asked 2010-01-16T18:44:23.393
49

C++ compilers automatically generate copy constructors and copy-assignment operators. Why not swap too?

These days the preferred method for implementing the copy-assignment operator is the copy-and-swap idiom:

T& operator=(const T& other)
{
    T copy(other);
    swap(copy);
    return *this;
}

(ignoring the copy-elision-friendly form that uses pass-by-value).

This idiom has the advantage of being transactional in the face of exceptions (assuming that the swap implementation does not throw). In contrast, the default compiler-generated copy-assignment operator recursively does copy-assignment on all base classes and data members, and that doesn't have the same exception-safety guarantees.

Meanwhile, implementing swap methods manually is tedious and error-prone:

  1. To ensure that swap does not throw, it must be implemented for all non-POD members in the class and in base classes, in their non-POD members, etc.
  2. If a maintainer adds a new data member to a class, the maintainer must remember to modify that class's swap method. Failing to do so can introduce subtle bugs. Also, since swap is an ordinary method, compilers (at least none I know of) don't emit warnings if the swap implementation is incomplete.

Wouldn't it be better if the compiler generated swap methods automatically? Then the implicit copy-assignment implementation could leverage it.

The obvious answer probably is: the copy-and-swap idiom didn't exist when C++ was developed, and doing this now might break existing code.

Still, maybe people could opt-in to letting the compiler generate swap using the same syntax that C++0x uses for control

Edit
Report

1 Answer

25

This is in addition to Terry's answer.

The reason we had to make swap functions in C++ prior to 0x is because the general free-function std::swap was less efficient (and less versatile) than it could be. It made a copy of a parameter, then had two re-assignments, then released the essentially wasted copy. Making a copy of a heavy-weight class is a waste of time, when we as programmers know all we really need to do is swap the internal pointers and whatnot.

However, rvalue-references relieve this completely. In C++0x, swap is implemented as:

template <typename T>
void swap(T& x, T& y)
{
    T temp(std::move(x));
    x = std::move(y);
    y = std::move(temp);
}

This makes much more sense. Instead of copying data around, we are merely moving data around. This even allows non-copyable types, like streams, to be swapped. The draft of the C++0x standard states that in order for types to be swapped with std::swap, they must be rvalue constructable, and rvalue assignable (obviously).

This version of swap will essentially do what any custom written swap function would do. Consider a class we'd normally write swap for (such as this "dumb" vector):

struct dumb_vector
{
    int* pi; // lots of allocated ints

    // constructors, copy-constructors, move-constructors
    // copy-assignment, move-assignment
};

Previously, swap would make a redundant copy of all our data, before discarding it later. Our custom swap function would just swap the pointer, but can be clumsy to use in some cases. In C++0x, moving achieves the same end result. Calling std::swap would generate:

dumb_vector temp(std::move(x));
x = std::move(y);
y = std::move(temp);

Which translates to:

dumb_vector temp;
temp.pi = x.pi; x.pi = 0; // te
answered 2010-01-16T21:38:24.150

Your Answer