Please take a look at this silly function, which should only illustrate the problem and a simplification of the real code:
struct A;
A create(bool first){
A f(21), s(42);
if(first)
return f;
else
return s;
}
I understand, that because it is not clear which object will be returned during the compilation, we cannot expect return value optimization (RVO) to be always performed.
However, one maybe could expect RVO to be performed in 50% of the cases (assuming uniform distribution for true
/false
due to lack of further information): just decide for which case RVO (first==true
or first==false
) should be performed and apply it for this parameter-value, accepting that in the other case the copy constructor must be called.
Yet this "partial RVO" is not the case for all compilers I can get my hands on (see live with gcc, clang and MSVC) - in both cases (i.e. first==true
or first==false
) the copy-constructor is used and not omitted.
Is there something, that renders the "partial RVO" in the above case invalid or is this an unlikely case of missed optimization by all compilers?
Complete program:
#include <iostream>
struct A{
int val;
A(int val_):val(val_){}
A(const A&o):val(o.val){
std::cout<<"copying: "<<val<<"\n";
}
};
A create(bool first){
A f(21), s(42);
if(first)
return f;
else
return s;
}
int main(){
std::cout<<"With true: ";
create(true);
std::cout<<"With false: ";
create(false);
}
Let's consider what happens if RVO is done for f
, meaning it is constructed directly in the return value. If first==true
and f
gets returned, great, no copy is needed. But if first==false
then s
gets returned instead, so the program will copy construct s
over the top of f
before the destructor for f
has run. Then after that, the destructor for f
will run, and now the return value is an invalid object that has already been destroyed!
If RVO is done for s
instead the same argument applies, except that now the problem happens when first==true
.
Whichever one you choose, you avoid a copy in 50% of cases and get undefined behaviour in the other 50% of cases! That's not a desirable optimization!
In order to make this work the order of destruction of the local variables would have to be altered so that f
is destroyed before copying s
into that memory location (or vice versa), and that's a very risky thing to mess with. The order of destruction is a fundamental property of C++ that should not be fiddled with, or you'll break RAII and who knows how many other assumptions.
My take on this, apart from reading Jonathan Wakely's answer with interest, is that one can always define a move constructor for the object being returned. This will then be favoured over the copy constructor if RVO cannot be applied for whatever reason and seems to me to be a good solution.
Things like std::vector
define such a constructor, so you get that for free.
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