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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With