Will C++ compiler optimize return-by-value code?

715 views Asked by At

Lets assume i use Visual Studio or modern GCC with -O2. Will compiler create S inside func() and then copy it to a my_result, or will it create my_result with constructor (5, 6, 5 + 6) without creating temporary S?

NOTE: Function func() definition and its usage are in separate .obj files!

struct S
{
    S(int _x, int _y, int _z) : x(_x), y(_y), z(_z) { }
    int x, y, z;
};

S func(int a, int b)
{
    return S(a, b, a + b);
}


/// USAGE ///

S my_result = func( 5, 6 );
5

There are 5 answers

1
jhauris On BEST ANSWER

Modern compilers will often optimize this kind of operation. See return value optimization

10
dwxw On

I would doubt the temporary is optimised out. You could test it by putting a print statement in the constructor and copy constructor and see what's printed under different compiler settings.

0
Mark B On

It's an optimization which pretty much by definition means it's optional for the compiler and up to each aprticular compiler to decide what to do. How can you find out for sure? Check the disassembly of the generated code!

That said most compilers should do this optimization (return value optimization [RVO]) as it's relatively easy to do in this case (no multiple returns, it's an unnamed temporary so you don't have aliasing, etc).

0
wilx On

It seems to me that the provided test case is simple enough for RVO to apply.

0
Yakk - Adam Nevraumont On

You can test this yourself, because the optimization in question has observable differences!

Most forms of optimization in C++ follow the as-if rule, which makes them difficult to detect. However, in a few cases, eliding (skipping) copy and move constructor is allowed, even if the difference results in observable behavior changes.

In this case, add the following to S:

struct S {
  // ...
  S( S const& o ):x(o.x), y(o.y), z(o.z) {
    std::cout << "copy ctor!\n";
  }
  S& operator=( S const& o ) {
    x=o.x;
    y=o.y;
    z=o.z;
    std::cout << "copy assign!\n";
    return *this;
  }
  S( S && o ):x(std::move(o.x)), y(std::move(o.y)), z(std::move(o.z)) {
    std::cout << "move ctor!\n";
  }
  S& operator=( S const& o ) {
    std::tie( x,y,z ) = std::tie( std::move(o.x),std::move(o.y),std::move(o.z) );
    std::cout << "move assign!\n";
    return *this;
  }
}

and run your code. With zero optimization you'll get copies and/or moves.

With any non-trivial level of optimization, the prints will disappear, because RVO (and, in related cases, NRVO) will run, eliminating the copies. (If your compiler isn't C++11, remove the move constructors above -- the optimization in C++03 was still allowed)

In C++11 you can explicitly construct the return value instead of relying on NRVO/RVO via the return {stuff} syntax.

Note that RVO (return value optimization) and NRVO (named return value optimization) are relatively fragile, and if you are relying on them you both have to understand how they work, what makes them break, and any quirks your particular compiler has in its implementation (if any).