|
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