3

I have the following code:

#include <iostream>
using namespace std;

  class foo
  {
      public:
          foo(int a, int b) :
              a_(a), b_(b)
          { cout << "regular const" << endl; }

          foo(const foo& f) :
              a_(f.a_), b_(f.b_)
          { cout << "copy const" << endl; }

          foo& operator=(const foo& rhs)
          {cout << "copy= const" << endl; a_ = rhs.a_; b_ = rhs.b_; return *this; }

          int a_, b_;
  };

  void bar(foo f)
  { }

  int main()
  {
      foo f1(10, 20);
      cout << "------" << endl;

      bar(f1);
      cout << "------" << endl;

      bar(foo(11, 22));            // line 29
      cout << "------" << endl;

      bar({13, 23});               // line 32
      cout << "------" << endl;    

  }

I get the following output:

$ ./a.out
regular const
------
copy const
------
regular const
------
regular const
------

For line 29 and line 32, I was expecting a temp object to be created in main (invoking regular constructor) and then a copy constructor being invoked when passed to bar(). From the output I see the compiler doing some optimization, and guessing maybe just creating the object on the stack when calling bar() and only regular constructor being invoked. Can someone please help me understand what type of optimization is being done or what is happening under the hood.

Is calling bar() using lines 29 and 32 equivalent as far as generated code? I understand line 29 is more readable.

I changed bar() as follows:

 void bar(const foo& f)
 { }

I get the same output for lines 29 and 32. In this case where is the object being created?

Thank you, Ahmed.

M.M
  • 138,810
  • 21
  • 208
  • 365
Ahmed A
  • 3,362
  • 7
  • 39
  • 57
  • copy-list-initialization and copy-elision do not construct temporary at the call side, the same with returning – Piotr Skotnicki Oct 19 '14 at 18:12
  • 1
    http://en.cppreference.com/w/cpp/language/copy_elision – Mat Oct 19 '14 at 18:12
  • @Mat Thank you for mentioning "copy elision", I did not run into this terminology before. And for providing the link. – Ahmed A Oct 19 '14 at 18:22
  • @AhmedA it's not only copy-elision, `bar({13, 23})` works also with copy-elision disabled via `-fno-elide-constructors` switch, because you are using copy-list-initialization syntax – Piotr Skotnicki Oct 19 '14 at 18:24
  • This is not duplicate... – Piotr Skotnicki Oct 19 '14 at 18:25
  • @PiotrS. However, as far as generated code, for line 29 and 32, are the following statements true? No temp object is created. The object is created on the stack when bar() is called. In that regards both statements are equivalent. Is that right? (Assuming -fno-elide-constructors is not passed to gcc). – Ahmed A Oct 19 '14 at 18:41
  • to clarify, when calling `bar( foo(11,22) )`, it behaves as if `bar` opens with `foo f = foo(11, 22);` – M.M Oct 19 '14 at 19:08

1 Answers1

3

In line 32,

bar({13, 23});

the parameter is initialized per copy-list-initialization - no intermediate temporary is created, not even theoretically.

bar(foo(11, 22));

Here, copy elision is involved. The compiler is allowed to elide the temporary, that is, construct the object directly into the parameter:

This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
[…]
— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

If you want to see the output without any elided copies or moves, use -fno-elide-constructors with GCC. It will produce the output you expect. At least for line 32.

Now to the second version of bar:

void bar(const foo& f)

In line 29, a temporary is created and initialized from the braced-init-list. In line 32, the reference is bound to the temporary argument. No optimizations or elisions are involved.

bar({13, 23});    // A temporary object is created as if by foo{13, 23},
                  // and the reference is bound to it

bar(foo(11, 22)); // The reference is bound to the temporary.
                  // The lifetime of the temporary has been extended.
Columbo
  • 60,038
  • 8
  • 155
  • 203
  • For the first version of bar(), is the generated code - as far as no temporary object being created and location of the created parameter object to bar, are they the same. Line 29 and Line 32? How about for the new version of bar()? – Ahmed A Oct 19 '14 at 18:33
  • @AhmedA Yes, after copy elision both lines are equivalent here. They are also equivalent for the new version of `bar`. – Columbo Oct 20 '14 at 09:38