Which function structure is better?
-
13-02-2021 - |
Question
Look at the following code:
class MyClass{
public:
MyClass(){}
MyClass(MyClass &&){}
MyClass(const MyClass &){}
};
MyClass f1(){
MyClass &&o=MyClass();
/*...*/
return std::move(o);//or return static_cast<MyClass &&>(o);
}
MyClass f2(){
MyClass o=MyClass();
/*...*/
return o;
}
int main(int, char **){
auto a=f1();
auto b=f2();
}
Function f2
is the normal form of returning an object. NRVO may apply and the additional copy constructor call could be avoid. f1
is the new form that uses rvalue reference. For systems that do not support NRVO but support rvalue reference, the move constructor is called rather than copy constructor, which would be considered better in most of the cases.
The problem of f1
is that: are there any compilers that support of NRVO in this case? It seems to be the better form in the future after all.
La solution
This is how current compilers (MSVC10 / gcc trunk) works :
Assuming MyClass is moveable
f1 : move
f2 :
worst case : move
best case : NRVO
Assuming MyClass is not moveable :
f1 : copy
f2 :
worst case : copy
best case : NRVO
So even if compilers get better and start doing NRVO for functions like f1, why bother complicate the code when f2 classical C++03 functions are already optimal ?
Autres conseils
are there any compiler support of NRVO in this case?
Define "compiler support"?
What f1
does is completely destroy the compiler's ability to optimize the copy of MyClass
. Let's look at f1
in detail
MyClass &&o=MyClass();
This creates a temporary, not a stack variable. Then that temporary is bound to an r-value reference called o
, which extends the lifetime of the temporary to the end of the function.
return std::move(o); //or return static_cast<MyClass &&>(o);
This returns an r-value reference of a stack-bound r-value reference to a temporary. And since you're returning a value, not a reference, the compiler has to create a temporary from it.
The copy/move of the temporary into a
will be elided. But you still created two temporaries (the original and the return value).
So f1
does the following:
create temporary
copy/move from temporary to return value
elide copy/move from return value to `a`.
f2
does:
create stack variable
elide copy/move from stack variable to `b`.
If NVRO doesn't exist, you have:
create stack variable
copy/move from stack variable to return value
elide copy/move from stack variable to `b`.
So, f2
is at worst equal to f1
. And more than likely, better.
Please stop trying to out-think the compiler. Just let copy elision do its job.