Boost logo

Boost :

Subject: Re: [boost] [variant] Basic rvalue and C++11 features support
From: Paul Smith (pl.smith.mail_at_[hidden])
Date: 2013-01-07 22:05:56


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.

>
>
> Regards,
> --
> Joel de Guzman
> http://www.boostpro.com
> http://boost-spirit.com
>
>
>
> _______________________________________________
> Unsubscribe & other changes:
> http://lists.boost.org/mailman/listinfo.cgi/boost

--
Paul Smith

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