4

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 );
wilx
  • 17,697
  • 6
  • 59
  • 114
pavelkolodin
  • 2,859
  • 3
  • 31
  • 74
  • Have you tried it? What's the result? – viraptor Sep 03 '13 at 14:00
  • 3
    It depends on the compiler and the optimization level and the platform and... everything. But I'd expect a decent compiler to do this optimization, at least in a release build. –  Sep 03 '13 at 14:00
  • @viraptor i don't even know how to check if this optimization done or not. – pavelkolodin Sep 03 '13 at 14:21
  • 1
    @pavelkolodin You can inspect the assembler output given by the compiler. For short binaries it's quite readable. For gcc that would be the `-S` option. VS will have it's own. – viraptor Sep 03 '13 at 14:28

5 Answers5

8

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

jhauris
  • 1,153
  • 11
  • 20
  • 1
    +1 for calling it Return Value Optimization instead of RVO so I can understand what it is without clicking on the link. – Prof. Falken Sep 03 '13 at 14:13
2

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).

Mark B
  • 95,107
  • 10
  • 109
  • 188
1

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

wilx
  • 17,697
  • 6
  • 59
  • 114
0

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.

dwxw
  • 1,089
  • 1
  • 10
  • 17
  • print statement prevents optimization :) – Alex F Sep 03 '13 at 14:04
  • Ehm, if you put a print statement there, the compiler can not optimize it away. – Prof. Falken Sep 03 '13 at 14:04
  • On the contrary. With optimizations enabled, the copy constructor is highly likely to be optimized away (see copy constructor elision). I would doubt the quality of any compiler that didn't. – Kaz Dragon Sep 03 '13 at 14:04
  • 1
    @AlexFarber: Copy elision is pretty much the only explicit exception to the "as if" rule. Side effects in a copy constructor don't keep the copy from being optimized away. – cHao Sep 03 '13 at 14:05
  • 1
    @KaxDragon The point is, as soon as there is some side effect (like output on the console), the compiler can no longer optimize it away. – user229044 Sep 03 '13 at 14:06
  • 4
    @meagar: Wrong. Copy ctor *can* be optimized out even if it has side effects. The C++ ISO standard specifically allows this. – wilx Sep 03 '13 at 14:07
  • 2
    @meagar The compiler is explicitly allowed to optimize away copy constructors in some circumstances *even if* they have side effects. – Mark B Sep 03 '13 at 14:07
  • 1
    @meagar ... except the copy constructor, which explicitly may be optimized away regardless of side effects. – Arne Mertz Sep 03 '13 at 14:07
  • @ArneMertz ... and the destructor (which would also be called if returning a copy). And the move constructor, since moves can also be elided. – Mike Seymour Sep 03 '13 at 14:10
  • @ArneMertz, why do you come here and scare us kids with facts? Optimize away side effects, that's just crazy talk. *holds ears and sings la-la-la* – Prof. Falken Sep 03 '13 at 14:11
0

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).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524