Search code examples
c++rvalue-referencecopy-elision

How to efficiently return an object when copy/move elision is disabled?


Assuming copy elision is disabled when compiling, is the following a valid thing to do to avoid unnecessary copying (simulating copy elision)?

Container getContainer() {
    Container c;

    return c;
}

int main() {
    Container&& contianer = getContainer();
}

Solution

  • Since C++17, some forms of copy elision ceased to be copy elision because the language simply no longer specifies the creation of a temporary anymore. Thus, there is nothing for the compiler to elide in the first place. For example, you can write this:

    Container container = getContainer();
    

    and there's no temporary object created for the return value of getContainer. Instead, the program directly constructs the result into container. Therefore, you do not need to write Container&& container = getContainer().

    On the other hand, NRVO is not guaranteed. When we look at this function definition:

    Container getContainer() {
        Container c;
    
        return c;
    }
    

    you are creating a local variable named c and requesting the move-initialization of the return object from c. If the compiler performs NRVO, then the variable c is elided. However, if you want a guarantee that the local Container object is elided (and thus no move is required) then the way to do that is to not declare a local Container object in the first place.

    In simple cases you can just create the return value at the last minute:

    Container getContainer() {
        // ...
        return Container();
    }
    

    There is no temporary object created by the above code (since C++17).

    However, if you need to construct the return value, do something with it, and then later return, you may need to declare your function to take an out-parameter of type Container&, which the function will mutate into the value that it wants to return. In some cases there might be no way for the caller to construct a Container object to pass into the function. In that case, the function must take a pointer argument, into which the value will be constructed using placement new. In that case the caller must take care to call the destructor manually at the end of the scope.