Avoiding copy of objects with the "return" statement

Vincent picture Vincent · May 7, 2012 · Viewed 23.6k times · Source

I have a very basic question in C++. How to avoid copy when returning an object ?

Here is an example :

std::vector<unsigned int> test(const unsigned int n)
{
    std::vector<unsigned int> x;
    for (unsigned int i = 0; i < n; ++i) {
        x.push_back(i);
    }
    return x;
}

As I understand how C++ works, this function will create 2 vectors : the local one (x), and the copy of x which will be returned. Is there a way to avoid the copy ? (and I don't want to return a pointer to an object, but the object itself)


What would be the syntax of that function using "move semantics" (which was stated in the comments)?

Answer

Matthieu M. picture Matthieu M. · May 7, 2012

There seems to be some confusion as to how the RVO (Return Value Optimization) works.

A simple example:

#include <iostream>

struct A {
    int a;
    int b;
    int c;
    int d;
};

A create(int i) {
    A a = {i, i+1, i+2, i+3 };
    std::cout << &a << "\n";
    return a;
}

int main(int argc, char*[]) {
    A a = create(argc);
    std::cout << &a << "\n";
}

And its output at ideone:

0xbf928684
0xbf928684

Surprising ?

Actually, that is the effect of RVO: the object to be returned is constructed directly in place in the caller.

How ?

Traditionally, the caller (main here) will reserve some space on the stack for the return value: the return slot; the callee (create here) is passed (somehow) the address of the return slot to copy its return value into. The callee then allocate its own space for the local variable in which it builds the result, like for any other local variable, and then copies it into the return slot upon the return statement.

RVO is triggered when the compiler deduces from the code that the variable can be constructed directly into the return slot with equivalent semantics (the as-if rule).

Note that this is such a common optimization that it is explicitly white-listed by the Standard and the compiler does not have to worry about possible side-effects of the copy (or move) constructor.

When ?

The compiler is most likely to use simple rules, such as:

// 1. works
A unnamed() { return {1, 2, 3, 4}; }

// 2. works
A unique_named() {
    A a = {1, 2, 3, 4};
    return a;
}

// 3. works
A mixed_unnamed_named(bool b) {
    if (b) { return {1, 2, 3, 4}; }

    A a = {1, 2, 3, 4};
    return a;
}

// 4. does not work
A mixed_named_unnamed(bool b) {
    A a = {1, 2, 3, 4};

    if (b) { return {4, 3, 2, 1}; }

    return a;
}

In the latter case (4), the optimization cannot be applied when A is returned because the compiler cannot build a in the return slot, as it may need it for something else (depending on the boolean condition b).

A simple rule of thumb is thus that:

RVO should be applied if no other candidate for the return slot has been declared prior to the return statement.