C++11 rvalue reference question

A

Andrew Tomazos

class O {...};
class I {...};
class E {...};

Suppose we have some function f that takes an instance of class I as
input and produces an instance of class O as output (throwing an
instance of class E on an error):

O f(const I& i)
{
O result;
... // mutate result based on i
if (...)
throw E(...);

return result;
}

I i = ...;
O o = f(i);

In C++03 many times it was recommend to write such a function as
follows:

void f(O& out, const I& i)
{
.... // mutate result based on i
if (,,,)
throw E(...);
}

I i = ...;
O o;
f(o,i);

I suspect this is to avoid the (potentially expensive) copy
construction as the temporary O instance is copied from f's stack
frame to the callers.

The downside is that you then can't do things like:

f(i).callSomeOFunction()

or

someFunctionTakingAnO(f(i))

My question is with the advent of move constructors, xvalues and
rvalue references in C++11, is there anyway to address this issue?

What would this mean/do:

O&& f(const I& i)
{
O result;
... // mutate result based on i
if (...)
throw E(...);

return result; // or std::forward(result) ?
}

or is it broken?

Thanks,
Andrew.
 
D

Daniel Krügler

class O {...};
class I {...};
class E {...};

Suppose we have some function f that takes an instance of class I as
input and produces an instance of class O as output (throwing an
instance of class E on an error):

O f(const I& i)
{
O result;
... // mutate result based on i
if (...)
throw E(...);

return result;
}

OK, so far.
I i = ...;
O o = f(i);

In C++03 many times it was recommend to write such a function as
follows:

void f(O& out, const I& i)
{
.... // mutate result based on i
if (,,,)
throw E(...);
}

I i = ...;
O o;
f(o,i);

I suspect this is to avoid the (potentially expensive) copy
construction as the temporary O instance is copied from f's stack
frame to the callers.
The downside is that you then can't do things like:

f(i).callSomeOFunction()

or

someFunctionTakingAnO(f(i))
Yes.

My question is with the advent of move constructors, xvalues and
rvalue references in C++11, is there anyway to address this issue?

What would this mean/do:

O&& f(const I& i)
{
O result;
... // mutate result based on i
if (...)
throw E(...);

return result; // or std::forward(result) ?
}

or is it broken?

This *is* broken, for the simple reason that this will produce a
"dangling" reference to an automatic object that has been locally
created in the function and that has been destroyed before the function
call ends. Don't do that!

Make it simple, just use return type O (without cv-qualifiers). The
language has intentionally been adapted to enforce the compiler to first
check whether the local object "result" can be be provided as an rvalue
to the return statement to enable move semantics if the type provides some.

HTH & Greetings from Bremen,

Daniel Krügler
 
A

Arne L

...
My question is with the advent of move constructors, xvalues and
rvalue references in C++11, is there anyway to address this issue?

What would this mean/do:

O&& f(const I& i)
{
O result;
... // mutate result based on i
if (...)
throw E(...);

return result; // or std::forward(result) ?

}

or is it broken?

That is broken. Never return rvalue references from functions.
Return by value instead. Even in C++98, there will be no
temporary in almost all cases. Search for "return value
optimization" and "named return value optimization".

rvalue references are primarily useful in the "perfect
forwarding" scenario, as well as during construction/
assignment of objects from function return values.

I i; // default constructor need be defined
O o = f(i);

In the second line, if RVO and NRVO fail, C++98
will call the copy constructor, meaning copying
of a temporary. In C++11, the move constructor
will be invoked (if it is defined), meaning NO
temporary will be copied. Note that RVO and
NRVO are frequently applicable (and you need
to do nothing to profit from it), so move
assignment may be more commonly profitable:

I i;
O o;
o = f(i);

In C++98, this will create a temporary in line 3,
neccessitating copying. In C++11, the move
assignment operator will be called (if it is defined),
meaning no copying will be performed.

In short: use return by value generously. Classes
from the standard library implement move semantics,
so don't be afraid to return them by value;

If you are writing your own classed (which own
resources), consider giving them move construction
and move assignment operator functions (as described
elsewhere).

Note that, generally, if you define one of the "big 5"
(destructor, copy ctor, assignment operator, move
ctor, move assignment operator), you will need to
define all 5. (Previously called the Rule Of The Big Three).

Greetings
Arne Luenser
 
D

Dave Abrahams

In C++03 many times it was recommend to write such a function as
follows:

void f(O& out, const I& i)
{
.... // mutate result based on i
if (,,,)
throw E(...);
}

I i = ...;
O o;
f(o,i);

I suspect this is to avoid the (potentially expensive) copy
construction as the temporary O instance is copied from f's stack
frame to the callers.

Probably, but the code above has always been a bad idea even in C++03.
Every compiler implements copy elision, so

O o = f(i);

is bound to be just as efficient, if not more so.

http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

Cheers,
 
S

SG

Yes. Make your type movable without using any && outside of move ctors
and move assignment operators. && is NOT some kind of compiler magic
that makes anything movable. It's there to ALLOW you to write MOVABLE
types.
[...]
Note that, generally, if you define one of the "big 5"
(destructor, copy ctor, assignment operator, move
ctor, move assignment operator), you will need to
define all 5. (Previously called the Rule Of The Big Three).

No, it's not *that* bad. The rule of three still remains the rule of
three, basically. And if it weren't for a little exception (C++03
compatibility) to a really simple new rule (about compiler-generated
copy/move operations), we wouldn't even need the rule of three
anymore. Luckily, this exception is deprecated which is why we now can
expect compilers to warn about rule-of-three violations. The simple
rule I was referring to earlier is:

- If you define any of the 5, the compiler won't generate any
other copy/move operations.

Obviously this is not compatible with existing C++03 code. So, unless
you declare a custom move operation (ctor or assignment or both) we're
back to C++03 rules w.r.t. compiler-generated operations.

So, if we put this together we'll get:

- user-declared move operations inhibit the generation of
all other copy/move operations.

- user-declared dtor or copy operations inhibit the
generation of all other move operations
(but not yet the remaining copy operations due to
C++03 compatibility)

I think it's safe to say that the situation actually improved.

Cheers!
SG
 
E

Ebenezer

Probably, but the code above has always been a bad idea even in C++03.
Every compiler implements copy elision, so

O o = f(i);

is bound to be just as efficient, if not more so.

I agree that's nice, but in a marshalling context, the other
form is able to handle multiple outputs better

O1 o1;
O2 o2;
Msgs.Receive(buf, o1, o2);


Brian Wood
Ebenezer Enterprises
http://webEbenezer.net
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top