Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is "partial RVO" not performed?

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);
}
like image 483
ead Avatar asked May 29 '19 09:05

ead


2 Answers

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.

like image 137
Jonathan Wakely Avatar answered Nov 09 '22 19:11

Jonathan Wakely


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.

like image 1
Paul Sanders Avatar answered Nov 09 '22 18:11

Paul Sanders