Should I Return an Rvalue Reference (By Std::Move'Ing)

Should I return an rvalue reference (by std::move'ing)?

So, lets say you have:

A compute()
{
A v;

return v;
}

And you're doing:

A a = compute();

There are two transfers (copy or move) that are involved in this expression. First the object denoted by v in the function must be transferred to the result of the function, i.e. the value donated by the compute() expression. Let's call that Transfer 1. Then, this temporary object is transferred to create the object denoted by a - Transfer 2.

In many cases, both Transfer 1 and 2 can be elided by the compiler - the object v is constructed directly in the location of a and no transferring is necessary. The compiler has to make use of Named Return Value Optimization for Transfer 1 in this example, because the object being returned is named. If we disable copy/move elision, however, each transfer involves a call to either A's copy constructor or its move constructor. In most modern compilers, the compiler will see that v is about to be destroyed and it will first move it into the return value. Then this temporary return value will be moved into a. If A does not have a move constructor, it will be copied for both transfers instead.

Now lets look at:

A compute(A&& v)
{
return v;
}

The value we're returning comes from the reference being passed into the function. The compiler doesn't just assume that v is a temporary and that it's okay to move from it1. In this case, Transfer 1 will be a copy. Then Transfer 2 will be a move - that's okay because the returned value is still a temporary (we didn't return a reference). But since we know that we've taken an object that we can move from, because our parameter is an rvalue reference, we can explicitly tell the compiler to treat v as a temporary with std::move:

A compute(A&& v)
{
return std::move(v);
}

Now both Transfer 1 and Transfer 2 will be moves.


1 The reason why the compiler doesn't automatically treat v, defined as A&&, as an rvalue is one of safety. It's not just too stupid to figure it out. Once an object has a name, it can be referred to multiple times throughout your code. Consider:

A compute(A&& a)
{
doSomething(a);
doSomethingElse(a);
}

If a was automatically treated as an rvalue, doSomething would be free to rip its guts out, meaning that the a being passed to doSomethingElse may be invalid. Even if doSomething took its argument by value, the object would be moved from and therefore invalid in the next line. To avoid this problem, named rvalue references are lvalues. That means when doSomething is called, a will at worst be copied from, if not just taken by lvalue reference - it will still be valid in the next line.

It is up to the author of compute to say, "okay, now I allow this value to be moved from, because I know for certain that it's a temporary object". You do this by saying std::move(a). For example, you could give doSomething a copy and then allow doSomethingElse to move from it:

A compute(A&& a)
{
doSomething(a);
doSomethingElse(std::move(a));
}

Safety of auto when std::move ing from a returned reference of a value

Is the usage of elementA safe standardized behaviour?

Yes.

... and what will be its type?

It's type will be std::string. auto type deduction works like template type deduction, and that includes removing the "referenceness" of a reference. The fact that std::move(container.front()) returns an xvalue doesn't really change much here. It is an "expiring" value, you can either (a) move-construct a new object (as you currently do) (b) bind it to a const-qualified reference or (c) bind it to an rvalue-reference. Here, (b) and (c) both work but don't make much sense, as they obscure the fact that nothing is moved-from (thanks to @M.M for correcting me here). Example:

auto elementA = std::move(container.front());
// Error, can't bind to non-const reference:
// auto& doesntWork = std::move(container.front());
auto&& thisWorks = std::move(container.front());
const auto& thisWorksToo = std::move(container.front());

Note that as @M.M pointed out in the comments, the last two references will be dangling once container.pop_front(); is encountered.

Also note that the deduction of elementB as std::string doesn't help the fact that you are dereferencing a moved-from object (return by container.front()), which you should avoid.

To move, or not to move from r-value ref-qualified method?

Well, since it's a r-value ref qualified member function, this is presumably about to expire. So it makes sense to move bar out, assuming Bar actually gains something from being moved.

Since bar is a member, and not a local object/function parameter, the usual criteria for copy elision in a return statement don't apply. It would always copy unless you explicitly std::move it.

So my answer is to go with option number one.

What is std::move(), and when should it be used?

Wikipedia Page on C++11 R-value references and move constructors

  1. In C++11, in addition to copy constructors, objects can have move constructors.

    (And in addition to copy assignment operators, they have move assignment operators.)
  2. The move constructor is used instead of the copy constructor, if the object has type "rvalue-reference" (Type &&).
  3. std::move() is a cast that produces an rvalue-reference to an object, to enable moving from it.

It's a new C++ way to avoid copies. For example, using a move constructor, a std::vector could just copy its internal pointer to data to the new object, leaving the moved object in an moved from state, therefore not copying all the data. This would be C++-valid.

Try googling for move semantics, rvalue, perfect forwarding.

Are return values going to be passed by rvalue reference in c++0x?

The rule is the following

  • If the compiler can do RVO, then it is allowed to do it, and no copy and no move is made.
  • Otherwise, the appropriate constructor is taken.

Like you say, the temporary is an rvalue, and thus the move constructor is selected, because of a rule in 13.3.3.2/3, which says that a rvalue reference binds to an rvalue better than an lvalue reference. In deciding whether to use the move or the copy constructor, overload resolution will therefor prefer the move constructor.

The rule that the compiler is allowed to perform RVO is written at 12.8/15.

Rvalue reference behavior while returning from a function

The problem here is that (as T.C. wraps up in the comment section) Wrapobj is a reference, not an object, therefore implicit moving - i.e. treating the returned lvalue as an rvalue - does not apply in this case (see [class.copy]/32).

You could fix this by writing:

Myclass Wrapobj = GetObj();

Instead of the current:

Myclass&& Wrapobj = GetObj();

Or by explicitly std::move()ing Wrapobj when returning it. I'd personally advise to go for the first option.

Should std::move be used in return-statements for effeciency?

Is one more efficient than the other since I would assume it is faster to move the new object out instead of copying it out?

Yes, using std::move here will be more efficient, assuming the object has move semantics more efficient than copying.

Usually, when returning a temporary or a local variable, move semantics will be used automatically. However, in this case you're not directly returning the temporary, but rather the lvalue reference returned by operator*=. Since an lvalue won't be moved, you do need std::move in this case to turn it into an rvalue.

However, you should not return a const value, as this prevents the return value from being used to move-initialise (or move-assign to) another object. Your example will initialise test2 by copying the return value, although that copy might be elided.

Alternatively, you could implement it using a local variable:

template<typename T> template <typename F> 
Object<T> Object<T>::operator*(const F& rhs) const
{
Object lhs(*this);
lhs *= rhs;
return lhs;
}

Not only can the return value be moved, but the move itself can be elided.

Do rvalue references allow dangling references?

Do rvalue references allow dangling references?

If you meant "Is it possible to create dangling rvalue references" then the answer is yes. Your example, however,

string middle_name () {
return "Jaan";
}

int main()
{
string&& nodanger = middle_name(); // OK.
// The life-time of the temporary is extended
// to the life-time of the reference.
return 0;
}

is perfectly fine. The same rule applies here that makes this example (article by Herb Sutter) safe as well. If you initialize a reference with a pure rvalue, the life-time of the tempoary object gets extended to the life-time of the reference. You can still produce dangling references, though. For example, this is not safe anymore:

int main()
{
string&& danger = std::move(middle_name()); // dangling reference !
return 0;
}

Because std::move returns a string&& (which is not a pure rvalue) the rule that extends the temporary's life-time doesn't apply. Here, std::move returns a so-called xvalue. An xvalue is just an unnamed rvalue reference. As such it could refer to anything and it is basically impossible to guess what a returned reference refers to without looking at the function's implementation.

Copy ctor called instead of move ctor

There are 2 things to understand in this situation:

  1. a in bar(Alpha &&a) is a named rvalue reference; therefore, treated as an lvalue.
  2. a is still a reference.

Part 1

Since a in bar(Alpha &&a) is a named rvalue reference, its treated as an lvalue. The motivation behind treating named rvalue references as lvalues is to provide safety. Consider the following,

Alpha bar(Alpha &&a) {
baz(a);
qux(a);
return a;
}

If baz(a) considered a as an rvalue then it is free to call the move constructor and qux(a) may be invalid. The standard avoids this problem by treating named rvalue references as lvalues.

Part 2

Since a is still a reference (and may refer to an object outside of the scope of bar), bar calls the copy constructor when returning. The motivation behind this behavior is to provide safety.

References

  1. SO Q&A - return by rvalue reference
  2. Comment by Kerrek SB


Related Topics



Leave a reply



Submit