Boost logo

Boost :

Subject: Re: [boost] rvalue ref best practices?
From: Dave Abrahams (dave_at_[hidden])
Date: 2012-06-15 00:39:01


on Tue Jun 12 2012, "Simonson, Lucanus J" <lucanus.j.simonson-AT-intel.com> wrote:

> Dave Abrahams wrote:
>>> If you aren't in a loop you probably don't care about performance. I
>>> know I intend to stick with pass by reference. It is very useful to
>>> clearly understand and be able to easily reason about the ownership of
>>> resources in a program.
>
>>If that's your primary concern, why on earth wouldn't you use pass-by-value?
>
> Let's take a sort of extreme example:
>
> A a;
> mutate(a, b); //a passed by reference as in/out
> mutate(a, c);
>
> vs.
>
> A a;
> a = mutate_rvr(std::move(a), b);
> a = mutate_rvr(std::move(a), c);
>
> Here I compare an accumulate semantic with and without move semantics.
> Both make zero copies. The latter results in four moves of a while
> the former has zero moves of a.

But that's not your primary concern.

> The former is less readable because you need to know that the first
> argument is the output. In my coding convention such functions are
> always named with a verb describing the action taken on the in/out
> parameter and the in/out is the first argument. A function that
> returns by value is named with a noun describing the return value. By
> convention I always know what is going on when I pass by reference for
> in/out parameters. The second allows equational reasoning, but
> composes the same since mutate returns a reference to A, again by my
> personal convention.

But it doesn't guarantee no aliasing. a and b could turn out to be the
same object, which means they share ownership of... whatever it is they
own.

> mutate(mutate(a,b),c);
>
> a = mutate_rvr(mutate_rvr(std::move(a), b),c);
>
> I just don't find it easier to think about an interface that requires
> that I transfer ownership of an object into it so that it can modify
> the object then return it by value to transfer ownership back.

That's the "wrong" way to think about it. You're not transferring
ownership; you're computing new values. Even the names you use muddle
this distinction. "mutate_rvr" says it's a logical mutation, when it
isn't. It's a noun, like you said. So:

     const X d = compute(compute(a, b), c);

is what you should start with, from the "thinking about it" point of
view. Then you might do things like these

     a = compute(compute(move(a), b), c);
     ^ ^^^^^^^

as optimizations.

> The reason I don't find it easier is because it is unnecessary and
> error prone. It is very easy to forget to call move.

Then performance is your concern, not eliminating the need to think
about ownership.

>
> A a;
> a = mutate_rvr(a, b);
> a = mutate_rvr(a, c);
>
> It will compile, function correctly passing tests, but it is now a
> performance bug.

Hmm.

    A a;
    a = compute(a, b);
    a = compute(a, c);

It would be nice if the compiler could insert those moves for us,
wouldn't it?

> To catch these bugs I need to put a logger in the
> copy constructor/assignment operator and run a special test to audit
> move/copy behavior. (or make A a move only type, which seems
> draconian)
>
> I also find copy Ellison and RVO harder to reason about than reference
> semantics because it is more complex and less explicit in the code to
> figure out whether the optimizations will be applied.
>
> I would rather use std::move in the special cases where I no longer
> should have ownership of an object in the current scope because the
> design intent is to transfer that ownership somewhere else (into a
> container, for example).

You know, I don't think of move in terms of ownership transfer unless
unique_ptr is involved.

> These are the places I am returning/passing by value in C++03. I
> don't know where to draw the line in changing coding style to make use
> of move semantics, but I don't think it should be drawn at either
> extreme, and I see very little downside to drawing it closer to the
> C++03 style that already works very well for me.

+1. Definitely do what works for you.

> There is no doubt that I would use move semantics everywhere that
> transfer of ownership is necessary and the performance advantage of
> move versus copy is beneficial. Everywhere else I'm inclined to
> continue to use references or make copies. We can do three things.
> We can copy, we can refer-to and we can transfer-ownership-of. There
> is plenty of occasion to need to do all three when programming. I
> would think that we should all just do whichever best fits our design
> intent in any given situation rather than try to prefer one over
> another.

I hate to say this, because I agree with what you've said,
but... "obviously." I get the sense you're arguing with the position of
someone who isn't around this list at the moment.

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