views:

683

answers:

4

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 controlling other implicit functions:

void swap() = default;

and then there could be rules:

  1. If there is a compiler-generated swap method, an implicit copy-assignment operator can be implemented using copy-and-swap.
  2. If there is no compiler-generated swap method, an implicit copy-assignment operator would be implemented as before (invoking copy-assigment on all base classes and on all members).

Does anyone know if such (crazy?) things have been suggested to the C++ standards committee, and if so, what opinions committee members had?

+2  A: 

Does anyone know if such (crazy?) things have been suggested to the C++ standards committee

Send an email to Bjarne. He knows all of this stuff and usually replies within a couple of hours.

FredOverflow
Or, much better, post the question to Usenet on comp.std.c++ which is the main public discussion point for standard C++ and the standardisation processes.
Richard
+9  A: 

swap, when used by STL algorithms, is a free function. There is a default swap implementation: std::swap. It does the obvious. You seem to be under the impression that if you add a swap member function to your data type, STL containers and algorithms will find it and use it. This isn't the case.

You're supposed to specialize std::swap (in the namespace next to your UDT, so it's found by ADL) if you can do better. It is idiomatic to just have it defer to a member swap function.

While we're on the subject, it is also idiomatic in C++0x (in as much as is possible to have idioms on such a new standard) to implement rvalue constructors as a swap.

And yes, in a world where a member swap was the language design instead of a free function swap, this would imply that we'd need a swap operator instead of a function - or else primitive types (int, float, etc) couldn't be treated generically (as they have no member function swap). So why didn't they do this? You'd have to ask the committee members for sure - but I'm 95% certain the reason is the committee has long preferred library implementions of features whenever possible, over inventing new syntax to implement a feature. The syntax of a swap operator would be weird, because unlike =, +, -, etc, and all the other operators, there is no algebraic operator everyone is familiar with for "swap".

C++ is syntactically complex enough. They go to great lengths to not add new keywords or syntax features whenever possible, and only do so for very good reasons (lambdas!).

Terry Mahaffey
UDT, ADL...WTF?
ergosys
user defined type, argument dependent lookup, what the f**k
Terry Mahaffey
I'm not under any such impression. I'm not talking about the freestanding `std::swap` function or about STL containers or algorithms at all, just about providing automatic `swap` member functions to objects, because it's easier for the compiler to do that than humans. That said, I realize that just providing the member function wouldn't allow for consistent syntax, so if consistency is needed, it'd probably need, say, its own operator.
jamesdlin
Updated my answer..
Terry Mahaffey
How about inventing a new operator `<->` for swap? :)
FredOverflow
@Fred: Like we don't have enough problems with < and > already...
the_drow
There's **gotta** be an ANSI symbol character left that C++ isn't using. Let's see... `` @ $. Digraphs? The dollar sign kinda looks like an S ($WAP).
Emile Cormier
@Emile: APL much?
Roger Pate
+1  A: 

Is even compiler-generated move constructor/assignment planned (with the default keyword)?

If there is a compiler-generated swap method, an implicit copy-assignment operator can be implemented using copy-and-swap.

Even though the idiom leaves the object unchanged in case of exceptions, doesn't this idiom, by requiring the creation of a third object, make chances of failure greater in the first place?

There might also be performance implications (copying might be more expensive than "assigning") which is why I don't see such complicated functionality being left to be implemented by the compiler.

Generally I don't overload operator= and I don't worry about this level of exception safety: I don't wrap individual assignments into try blocks - what would I do with the most likely std::bad_alloc at that point? - so I wouldn't care if the object, before they end up destroyed anyway, remained in the original state or not. There may be of course specific situations where you might indeed need it, but I don't see why the principle of "you don't pay for what you don't use" should be given up here.

UncleBens
"you don't pay for what you don't use". If you never catch exceptions, and never write destructors which assume any objects are in a valid state, then you don't need exception guarantees. Other users of your classes might catch exceptions sometimes, and do things in destructors. They need exception guarantees. Unfortunately it's not possible always to provide exception guarantees so that you never pay if you don't use them. Fortunately if your class holds resources, and on assignment would have to free the old resource and allocate a new one, then CAS is no extra expense.
Steve Jessop
+13  A: 

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; // temp(std::move(x));
x.pi = y.pi; y.pi = 0; // x = std::move(y);
y.pi = temp.pi; temp.pi = 0; // y = std::move(temp);

The compiler will of course get rid of redundant assignment's, leaving:

int* temp = x.pi;
x.pi = y.pi;
y.pi = temp;

Which is exactly what our custom swap would have made in the first place. So while prior to C++0x I would agree with your suggestion, custom swap's aren't really necessary anymore, with the introduction of rvalue-references. std::swap will work perfectly in any class that implements move functions.

In fact, I'd argue implementing a swap function should become bad practice. Any class that would need a swap function would also need rvalue functions. But in that case, there is simply no need for the clutter of a custom swap. Code size does increase (two ravlue functions versus one swap), but rvalue-references don't just apply for swapping, leaving us with a positive trade off. (Overall faster code, cleaner interface, slightly more code, no more swap ADL hassle.)

As for whether or not we can default rvalue functions, I don't know. I'll look it up later or maybe someone else can chime in, but that would sure be helpful. :)

Even so, it makes sense to allow default rvalue functions instead of swap. So in essence, as long as they allow = default rvalue functions, your request has already been made. :)

EDIT: I did a bit of searching, and the proposal for = default move was proposal n2583. According to this (which I don't know how to read very well), it was "moved back." It is listed under the section titled "Not ready for C++0x, but open to resubmit in future ". So looks like it won't be part of C++0x, but may be added later.

Somewhat disappointing. :(

EDIT 2: Looking around a bit more, I found this: Defining Move Special Member Functions which is much more recent, and does look like we can default move. Yay!

GMan
That makes sense. I haven't quite wrapped my head around the new rvalue reference stuff and suspected that might be a factor. From what little I've read, it does sound like classes will need to explicitly implement a move-constructor, which would have similar maintenance burdens as implementing `swap`, unless there's a way to opt-in to an implicit definition with `... = default`. I suppose even if there isn't, at least compilers might have an easier time generating warnings for incomplete implementations.
jamesdlin
@james Yea, actually more of a burden since you need a move-constructor and move-assignment operator. Probably a good trade-off for all the other things rvalue-references provide. I really hope we can make `= default` r-value functions.
GMan
Do rvalue constructors and assignments have to be nothrow, or is it just strongly recommended (to get a nothrow `std::swap` for your class)? For instance if you have as a member a legacy class with no rvalue ctor, then you might be tempted to write an rvalue ctor that moves most of the data but copies that bit. That's death for the default swap implementation you give, it wouldn't necessarily even be strongly-safe, let alone nothrow. So unless I've missed something cunning, you need to implement swap yourself, to call swap on the legacy member, bad practice or not.
Steve Jessop
@Steve: I don't have the article on me, but from what I remember it seems it's only strongly recommended.
GMan
Defaulting move makes for interesting pitfalls that are similar to, but distinctly different from defaulting the copy contructor.
Omnifarious