Boost logo

Boost :

Subject: Re: [boost] [move][container] Review Request (new versions of Boost.Move and Boost.Container in sandbox and vault)
From: David Abrahams (dave_at_[hidden])
Date: 2009-09-07 07:53:04


on Mon Aug 24 2009, Thomas Klimpel <Thomas.Klimpel-AT-synopsys.com> wrote:

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

I disagree. It's OK if transforming "x" into "move(x)" changes
semantics with respect to x. It isn't OK if it changes semantics with
respect to objects other than x. In particular,

        y = x; ===> y = move(x);

should not change anything about what happens with y. That's just too
capricious.

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

Here's another example:

       // C++03
       template <class Pair, class T>
       T& replace2nd(Pair& c, T const& x)
       {
           return c.second = x;
       }
       
       pair<thread,thread> threads;
       ...

       long_running_operation(replace2nd(threads, new_thread())); // (**)

OK, let's move-enable replace2nd:
       
       // addtional overload
       template <class Pair, class T>
       T& replace2nd(Pair& c, T&& x)
       {
           return c.second = move(x);
       }

can't clear there; we don't know that T has a clear().

Now we've just changed the semantics of the line marked (**), since the
old thread won't be canceled until the long-running operation is
complete.

Replace "thread" with "lock" and now you can easily deadlock, because
you've messed with tghe locking/unlocking order. I'm pretty sure we can
massage this example easily to eliminate even the appearance of move()
in the code. Oh, sure:

       // C++03
       template <class Pair, class F>
       T& replace2nd(Pair& c, F const& f)
       {
           return c.second = f(); // f might return an rvalue
       }

>> 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,

The other requirement is that user-observable side-effects of assignment
from an lvalue must be preserved. If you own a T that's an
implementation detail, you can swap it away and nobody's the wiser. If,
as in vector<T>, pair<T>, shared_ptr<T>, etc., you have a T that the
user can observe, you need to make sure it is destroyed.

> so if the programmer expects something
> incompatible with this implementation, he is relying on undefined
> behavior.

Huh? How do you reach that conclusion?

> 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
             ^--- usually
> temporary object that will soon be destroyed or an object that will
> soon be (move-)? assigned a new value.

Exactly. The capacity of a vector can be valuable when assigning.

   template <class It, class T>
   void push_front_like(It start, It finish, T const& x)
   {
       while (--finish != start)
       {
           It prev = finish;
           --prev;
           *finish = std::move(*prev);
       }

       // If there's enough capacity left in *start, we avoid a
       // deallocation and an allocation
       *start = x;
   }

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

You can't do any of that if you don't know anything special about the
type of the 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();."

Exactly. I think Howard just didn't know how to phrase the requirement
on user-visible side-effects. Credit where due: he's the one who
explained this to me.

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

I disagree. The capacity of a vector is not part of its value.

-- 
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

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