Is there any case where a return of a RValue Reference (&&) is useful?
There are a few occasions when it is appropriate, but they are relatively rare. The case comes up in one example when you want to allow the client to move from a data member. For example:
template <class Iter>
class move_iterator
{
private:
Iter i_;
public:
...
value_type&& operator*() const {return std::move(*i_);}
...
};
Should I return an rvalue reference parameter by rvalue reference?
There's no right answer, but returning by value is safer.
I have read several questions on SO relating to returning rvalue references, and have come to the conclusion that this is bad practice.
Returning a reference to a parameter foists a contract upon the caller that either
- The parameter cannot be a temporary (which is just what rvalue references represent), or
- The return value won't be retained past the the next semicolon in the caller's context (when temporaries get destroyed).
If the caller passes a temporary and tries to save the result, they get a dangling reference.
From what I have read, it seems the consensus is that since return values are rvalues, plus taking into account the RVO, just returning by value would be as efficient:
Returning by value adds a move-construction operation. The cost of this is usually proportional to the size of the object. Whereas returning by reference only requires the machine to ensure that one address is in a register, returning by value requires zeroing a couple pointers in the parameter std::string
and putting their values in a new std::string
to be returned.
It's cheap, but nonzero.
The direction currently taken by the standard library is, somewhat surprisingly, to be fast and unsafe and return the reference. (The only function I know that actually does this is std::get
from <tuple>
.) As it happens, I've presented a proposal to the C++ core language committee toward the resolution of this issue, a revision is in the works, and just today I've started investigating implementation. But it's complicated, and not a sure thing.
std::string transform(std::string&& input)
{
return transform(input); // calls the lvalue reference version
}
The compiler won't generate a move
here. If input
weren't a reference at all, and you did return input;
it would, but it has no reason to believe that transform
will return input
just because it was a parameter, and it won't deduce ownership from rvalue reference type anyway. (See C++14 §12.8/31-32.)
You need to do:
return std::move( transform( input ) );
or equivalently
transform( input );
return std::move( input );
Difference between returning an rvalue reference and a value in && qualified functions
When you use this
reference qualifiers in this way, you have to ask yourself two questions:
- What does
std::move(object).funcname()
mean? - What does
Typename().funcname()
mean?
If you return a value from the function, then both of those will mean the same thing. Regardless of what you do to capture the value, the value will be a whole and distinct object, move-constructed from some internal data stored in the object.
In the first case, object
now potentially no longer owns the data. In the second case, it doesn't matter because the object was a temporary and has since been destroyed.
If you return a &&
from the function, then those will mean different things. Namely, #2 will mean "your code is broken".
As to why you might still want to do it, even if it allows broken code. Well, that has to do with the actual answers to that question: what do those things mean?
Here's what I am referring to. std::get
is basically a member function of tuple
. And yet, if you pass it a tuple&&
, you will get a T&&
returned and not a T
. Why?
Because you're accessing a member of the tuple
.
If you had a struct
, then std::move(struct_object).x
would be an rvalue reference as well. So std::get
is simply behaving in the same way for a tuple
as member access would for a struct
. That's kinda the whole point of tuple
, after all: to behave like a struct
as much as possible.
This allows you to do things like std::get<0>(std::move(tpl)).member
, such that member
will still be an rvalue reference. So you can move from a subobject without disturbing the rest of the object, exactly as you could for any other rvalue reference member accesses.
If get
returned a value, then this would do something very different. Regardless of what we do with the return value, it will be moved out of the object, no questions asked. So if we only wanted to move a subobject of that member... too bad. The original object lost the entire member, not just a subobject of that member.
Of course, that doesn't change the fact that:
auto &&x = SomeStruct().x; //This extends the temporary's lifetime
auto &&x = std::get<0>(SomeTuple(...)); //This gets a dangling reference.
That is an unfortunate limitation of the language. But if the function is logically a member accessor, then returning a &&
from a &&
qualified this
function instead of a value is a legitimate choice.
It all depends on what matters more to you: safety or orthogonality with member accessors.
C++ Why does returning rvalue reference change caller's behavior when function signature does not return rvalue reference?
What's happening is you are seeing elision there. You are move-constructing on return std::move(x)
with a simple type of Bar
; then the compiler is eliding the copy.
You can see the non-optimized assembly of GetBarRValue
here. The call to the move constructor is actually happening in the GetBarRValue
function, not upon returning. Back in main
, it's just doing a simple lea
, it's not at all calling any constructor.
Is returning by rvalue reference more efficient?
Beta_ab&&
Beta::toAB() const {
return move(Beta_ab(1, 1));
}
This returns a dangling reference, just like with the lvalue reference case. After the function returns, the temporary object will get destructed. You should return Beta_ab
by value, like the following
Beta_ab
Beta::toAB() const {
return Beta_ab(1, 1);
}
Now, it's properly moving a temporary Beta_ab
object into the return value of the function. If the compiler can, it will avoid the move altogether, by using RVO (return value optimization). Now, you can do the following
Beta_ab ab = others.toAB();
And it will move construct the temporary into ab
, or do RVO to omit doing a move or copy altogether. I recommend you to read BoostCon09 Rvalue References 101 which explains the matter, and how (N)RVO happens to interact with this.
Your case of returning an rvalue reference would be a good idea in other occasions. Imagine you have a getAB()
function which you often invoke on a temporary. It's not optimal to make it return a const lvalue reference for rvalue temporaries. You may implement it like this
struct Beta {
Beta_ab ab;
Beta_ab const& getAB() const& { return ab; }
Beta_ab && getAB() && { return move(ab); }
};
Note that move
in this case is not optional, because ab
is neither a local automatic nor a temporary rvalue. Now, the ref-qualifier &&
says that the second function is invoked on rvalue temporaries, making the following move, instead of copy
Beta_ab ab = Beta().getAB();
Returning by rvalue reference vs returning by value
Why not return by value and internally move the stored object?
Because that could be less efficient than returning a reference. Consider a case where you use the returned reference to fetch another reference from within that object. Like for example (*get_vector())[3]
. With your proposed change, that is a reference to a copy of the original; the way it is currently, it is a reference to a value in the original temporary.
C++ as a language doesn't deal well effectively with the lifetime of references to temporaries. The solutions currently consist of either being careful about lifetimes, or not using references and having potentially slower/less efficient code. The standard library, in general, prefers to err on the side of performance.
Related Topics
Gcc C++ "Hello World" Program -> .Exe Is 500Kb Big When Compiled on Windows. How to Reduce Its Size
Using Class Name in a Class Template Without Template Parameters
Faster Bulk Inserts in SQLite3
How to Correctly and Standardly Compare Floats
Extracting C/C++ Function Prototypes
How to Get the Size of a Memory Block Allocated Using Malloc()
Nonstatic Member as a Default Argument of a Nonstatic Member Function
How to Embed a File into an Executable
Cpp - Valgrind - Invalid Read of Size 8
How to Disassemble a Binary Executable in Linux to Get the Assembly Code
Relative Performance of Std::Vector VS. Std::List VS. Std::Slist