Search code examples
c++c++11movervo

Return Value Optimization and functions that build structures


I have a function that "builds" a structure to return:

struct stuff {
    int a;
    double b;
    Foo c;
};
stuff generate_stuff() {
    Foo c = generate_foo();
    //do stuff to Foo, that changes Foo:
    //...
    return {1, 2.0, c};  //should this be return {1, 2.0, move(c)};?
}

Should I be moving c out of the function? I realize that frequently, (N)RVO can build the object in place, however there might be times when this ISN'T the case. When can't (N)RVO be done, and thus, when should I move an object of a function?

Put another way, this is obviously going to be RVO of the temporary that is returned. The question becomes, will NRVO (named return value optimization) happen with c? Will c be constructed in place (at the call site of the function, inside the temporary stuff structure), or will c be constructed in the function, and then copied into the structure at the call site.


Solution

  • What your call to move(c) does is not moving c out of the function, but moving it into the temporary struct that is returned from the function. The temporary return value should always benefit from RVO. However, I believe that the move/copy of c into the temporary cannot be optimized away, as c itself is not a temporary. So here the move version should always be at least as efficient as the copy version (Tested for a simple scenario with g++, clang++ and MVC++).

    If you have to absolutely minimize the number of copy/move operations, then you could write

    struct stuff {
        int a;
        double b;
        Foo c;
    };
    stuff generate_stuff() {
        stuff s{ 1, 2.0, generate_foo() };
        //use s.c instead of c
        //...
        return s;  
    }
    

    which would result in only a single construction of Foo and no copies/moves thanks to NRVO.

    EDIT: As @dyp pointed out in the comments to your question, the in-place construction of Stuff isn't actually a case of RVO, but required by the standard. Anyway, the important part is that the move/copy of c cannot be elided so that using move should never result in a performance penalty.