When returning an object from a function one of the following cases can happen since C++11, assuming move- and copy-constructors are defined (see also the examples at the end of this post):
The advice for the first 3 cases is not to use the explicit std::move
, because move is performed anyway and could prevent possible RVO, see for example this SO-post.
However, in the 4. case an explicit std::move
would improve the performance. But as somebody who is fluent in reading neither the standard nor the resulting assembler, it takes a lot of time to differentiate between cases 1-3 and 4.
Thus my question: Is there are a way to handle all of the above cases in an unified manner, such that:
Here are some examples, which also can be used as test cases.
All examples use the following helper-class-definition:
struct A{
int x;
A(int x_);
A(const A& a);
A(A&& a);
~A();
};
1. example: 1.case, RVO performed, live-demonstration, resulting assembler:
A callee1(){
A a(0);
return a;
}
2. example: 1.case, RVO performed, live-demonstration, resulting assembler:
A callee2(bool which){
return which? A(0) : A(1);
}
3. example: 2.case, qualifies for copy-elision, RVO not performed, live-demonstration, resulting assembler:
A callee3(bool which){
A a(0);
A b(1);
if(which)
return a;
else
return b;
}
4. example: 3.case, doesn't qualify for copy-elision (x
is a function-parameter), but for moving, live-demonstration, resulting assembler:
A callee4(A x){
return x;
}
5. example: 4.case, no copy-elision or implicit moving (see this SO-post), live-demonstration, resulting assembler:
A callee5(bool which){
A a(0);
A b(1);
return which ? a : b;
}
6. example: 4.case, no copy-elision or implicit moving, live-demonstration, resulting assembler:
A callee6(){
std::pair<A,int> x{0,1};
return x.first;
}
The compiler (usually) can't perform RVO if any of the following apply:
In case 1, you should either use std::move
or write the code using an if
statement instead of the ternary operator. In case 2, you have to use std::move
. In case 3, you should also use std::move
explicitly.
Handling case 1. We can ensure the value being moved by relying on if
statements for returning local variables, instead of the ternary operator:
A whichOne(bool which) {
A a(0);
A b(1);
if(which) {
return a;
} else {
return b;
}
}
If you really prefer using the ternary operator, use std::move
explicitly on both conditionals:
A whichOne(bool which) {
A a(0);
A b(1);
return which ? std::move(a) : std::move(b);
}
What if I only want to move a
, but not b
? We can handle this case by explicitly constructing it in the conditional. In this case, the constructed value is guaranteed to undergo RVO, since both sides of the ternary produce a prvalue.
A whichOne(bool which) {
A a(0);
A b(1);
return which
? A(std::move(a)) // prvalue move-constructed from a
: A(b); // prvalue copy-constructed from b
}
Handling case 2 and 3. This is pretty straight-forward: use std::move
when returning a member of an object, or when returning a reference that came from an array or pointer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With