Boost logo

Boost :

Subject: Re: [boost] [variant] Basic rvalue and C++11 features support
From: Joel de Guzman (djowel_at_[hidden])
Date: 2013-01-08 06:18:09


On 1/8/13 11:05 AM, Paul Smith wrote:
> On Tue, Jan 8, 2013 at 2:04 AM, Joel de Guzman <djowel_at_[hidden]> wrote:
>> On 1/8/13 4:14 AM, Paul Smith wrote:
>>>
>>> A recursive_wrapper is not a pointer. It's a value-like wrapper
>>> that is assumed to always contain a valid object. The move
>>> constructor should leave the moved-from recursive_wrapper
>>> in a valid state, which precludes nullifying it.
>>> That is, unless you suggest adding an "empty" state to
>>> recursive_wrapper, which doesn't sound like a very good
>>> idea.
>>
>>
>> I disagree. That state will happen only when copying rvalues which
>> will immediately be destructed anyway. What danger do you see in
>> that situation? Example:
>>
>> recursive_wrapper<foo> bar() {...} // function returning
>> recursive_wrapper
>>
>> recursive_wrapper<foo> foo(bar()); // copy
>>
>> Under no circumstances will anyone get to see that "empty" state.
>> Do you see something that I don't?
>>
>> Without this move optimization (as it currently is), it is very inefficient
>> especially with big structures (e.g. tuples and fusion adapted structs).
>> Without this optimization, such temporary copies will end up with two
>> heap allocations and unnecessary copying of the structures, instead of
>> one heap allocation and a simple pointer swap. That would mean the missed
>> optimization in the order of magnitudes with applications that use variant
>> heavily (e.g. Spirit).
>
> I hear ya. However, the C++11 take on move semantics is, for better
> and for worse, a conservative one and not a destructive one. It's a
> common misconception that a moved-from object is "as good as dead" and
> that the move constructor can perform an autopsy. (It took me a while
> to realize that, too).
>
> A move constructor has merely a "license to mutate" the source object
> while keeping it's invariants, not a "license to kill". This is, I
> believe, the intended convention. This is more or less what the STL
> expect from client types. And, even though it's not enforced by the
> core language, the effective semantics of rvalues and
> rvalue-references are such that a destructive move is unsafe. In
> particular, subobjects of rvalues are rvalues of the same value
> category, and can equally well bind to rv-refs. Even if a top-level
> rvalue is immediately destroyed after one of its subojects has been
> moved-from, its destructor might still attempt to use that subobject.
> If a type gives me public access to one of it's subobjects, it means
> that I'm allowed to mutate it using it's public interface any way I
> want, but nothing more. If the subobject's move consutrctor (which I
> can rightfully invoke) does something that's impossible to acheive
> using the public interface, I'm breaking this contract, and even if I
> no longer use the containing object, I might get punished when it's
> destructor takes place:
>
> class base {
> void something_naughty(); // don't attempt to use the
> // object after calling me
> public:
> base();
> base(base&& other) {other.something_naughty();}
>
> void something_ordinary();
> };
>
> struct derived : base {
> ~derived() {something_ordinary();}
> };
>
> int main() {
> base b = derived(); // I'm only using the public interface
> // of derived. How did I manage to
> // screw something up?
> }
>
> Note that this also applies to data members.
> Also, note that rvalues can ofcourse be std::moved lvalues, in which
> case they can be accessed by the programmer after having moved them,
> and he might wish to reuse them (or, if the lvalue is an lv-ref
> parameter, he might not know if whoever passed him this lvalue does),
> even though, admittedly, I haven't seen many convincing examples of
> such cases.
>
> I agree that the cases for which a destructive move is appropriate
> seem by far the dominant ones (even if it's no different than a
> conservative move in many of them). If you're asking for a rationale
> as to why the semantics are the way they are, then you're talking to
> the wrong person. I am sure David Abrahams can give a much more
> credible answer than I can. Some of these issues are discussed in
> N1377, in particular, see the "Alternative Move Designs" ->
> "Destructive move semantics" section
> (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#Alternative
> move designs)
> (Also note that the "Copy vs Move" section literally states: "The only
> requirement (from the move-ctor PS) is that the (source PS) object
> remain in a self consistent state (all internal invariants are still
> intact).")
>
> FWIW, I'm playing with the idea of writing a destructive-move proposal
> (although what I'm reffering to as destructive-move is a bit different
> than what the move proposal calls a destructive-move), and these kind
> of cases are exactly what I wish to address. If you have actual
> numbers that demonstrate how bad is the impact on Spirirt and co, I'd
> love to hear them.

Those are very good points, but I think you are being too pedantic.
The example you posted concerns a class hierarchy (base / derived).
The paper you cited mentions:

   "When dealing with class hierarchies, destructive move semantics
   becomes problematic."

That is certainly not the case with recursive_wrapper. Do you see
a reasonable cause for danger with (a non-hierarchical struct such
as) recursive_wrapper?

Finally, it would be best if you quote from the standard. That paper
you posted is quite old (from 2002).

Regards,

-- 
Joel de Guzman
http://www.boostpro.com
http://boost-spirit.com

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