Boost logo

Boost :

Subject: Re: [boost] [move][container] Review Request (new versions of Boost.Move and Boost.Container in sandbox and vault)
From: Thomas Klimpel (Thomas.Klimpel_at_[hidden])
Date: 2009-08-24 19:16:42


Ion Gaztañaga wrote:
> Thomas Klimpel escribió:
> > So my position is that the user better be prepared to expect that "a
> > =
> boost::move(b); " can be implemented as "a.swap(b)", because move
> assignment is about efficiency, and a.swap(b) is often the most
> efficient implementation of move assignment.
>
> Some additional discussion can be found in this thread:
>
> http://lists.boost.org/Archives/boost/2007/04/119872.php
>
> I was also against clear(), but somehow people convinced me and I added
> clear() to my classes ;-)

I have read that thread now. I didn't have the impression that this thread really reached a conclusion. I like the idea of Howard Hinnant to examine the (generic) algorithms that work with move semantics to determine how move-construction and move-assignment should behave. However, the example he gives why he prefers the semantics of clear(); swap(); is not a (generic) algorithm but an example with concrete types:

Howard Hinnant wrote:
> Now consider vector<thread>:
> vector<thread> v1;
>...
> vector<thread> v2;
>...
> v1 = std::move(v2);
> After the move assign, I think it best that whatever threads v1
> happened to previously refer to, are now busy canceling themselves,
> instead of continuing to run under v2's ownership. If you want the
> latter, you've got swap.

If the user wants to ensure that the threads get canceled, he can call "v2.clear();" himself.

So the question for me would be whether there are (generic) algorithms (because they can't call x.clear() themselves) that will use more resources if the move-assignment doesn't call x.clear() for them. After all, those (generic) algorithms are the real clients that will call std::move (x) and will have to deal with non-temporary moved from objects.

> The programmer has not required any value swapping just a "transfer" and
> I think the programmer expects all previous v1 values should have been
> destroyed.

To be honest, it is exactly because of this argument why I don't like the clear() in the assignment operator of vector. The words "The only requirement is that the object remain in a self consistent state (all internal invariants are still intact)." don't forbid to implement move-assignment as swap, so if the programmer expects something incompatible with this implementation, he is relying on undefined behavior. The move-constructor and move-assignment must be implemented for every class that wants to implement move semantics, so it's best not to burden these with unnecessary requirements and expectations.

On the other hand, there might be other good reasons to call clear() in the assignment operator, or more generally try to free the resources owned by any no longer needed r-value. The resources left in the r-value won't be reused anyway, because the r-value is either a temporary object that will soon be destroyed or an object that will soon be (move-)? assigned a new value.

The typical scenario where it would be helpful if the r-value is left without owning resources is a function that forwards some of its (r-value) argument to another function, but has no information about the forwarded arguments except that the other function requires these as arguments. All resources left in the r-value would be around at least until the function exits, and the might be a waste of resources. So it seems to be a good idea to advise any function that takes an r-value argument to try to free the resources owned by its r-value argument before returning, either by explicitly freeing the resources or by forwarding the r-value to another function (thereby forwarding the responsibility to free the resources owned by its r-value argument).

> This has been discussed before, but I agree that we should
> have a consensus on these issues. Maybe committee members have some
> guidelines for move semantics.

I found the following
http://www.open-std.org/JTC1/sc22/WG21/docs/papers/2002/n1377.htm

"The difference between a copy and a move is that a copy leaves the source unchanged. A move on the other hand leaves the source in a state defined differently for each type. The state of the source may be unchanged, or it may be radically different. The only requirement is that the object remain in a self consistent state (all internal invariants are still intact). From a client code point of view, choosing move instead of copy means that you don't care what happens to the state of the source."

Both Peter Dimov and Howard Hinnant participated in the above thread. I had the impression that they still consider the above requirements sufficient. However, Howard Hinnant also stated in the above thread "Originally I preferred vector move assignment as just swap(). However I've recently become convinced that this is not the best definition. I now prefer the semantics of clear(); swap();."

Even if the advice

"Any function that takes an r-value argument should try to free the resources owned by its r-value argument before returning, either by explicitly freeing the resources or by forwarding the r-value to another function (thereby forwarding the responsibility to try to free the resources owned by its r-value argument)."

would be present in some important place, the semantics of 'clear(); swap();' for move assignment would still be questionable, because the memory owned by the vector is not freed.

So what's my own opinion on this topic now? Trying to free resources owned by r-value arguments before returning is probably a good idea, because it is often what the programmer expects, it improves decoupling of concerns and reduces unexpected surprised when forwarding of r-value arguments. As a consequence, even so implementing the move-assignment operator as a pure swap looks temping at first sight, this is not the best definition.

Regards,
Thomas


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk