开发者

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

开发者 https://www.devze.com 2022-12-16 09:59 出处:网络
C++ compilers automatically generate copy constructors and copy-assignment operators. Why not swap too?

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 开发者_StackOverflow中文版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?


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!


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!).


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消