20
class MyClass
{
public:
  ~MyClass() {}
  MyClass():x(0), y(0){} //default constructor
  MyClass(int X, int Y):x(X), y(Y){} //user-defined constructor
  MyClass(const MyClass& tempObj):x(tempObj.x), y(tempObj.y){} //copy constructor

private:
  int x; int y;
};

int main()
{
  MyClass MyObj(MyClass(1, 2)); //user-defined constructor was called.
  MyClass MyObj2(MyObj); //copy constructor was called.
}

In the first case, when MyClass(1, 2) calls the user-defined constructor and returns an object, I was expecting MyObj to call the copy constructor. Why it doesn't need to call the copy constructor for the second instance of MyClass?

Wolf
  • 9,679
  • 7
  • 62
  • 108
cpx
  • 17,009
  • 20
  • 87
  • 142
  • 1
    It's because of copy elision optimization by the compiler. Adding **-fno-elide-constructors** option to g++ while compiling will disable that optimization. – Vencat Sep 08 '19 at 06:30

4 Answers4

41

Whenever a temporary object is created for the sole purpose of being copied and subsequently destroyed, the compiler is allowed to remove the temporary object entirely and construct the result directly in the recipient (i.e. directly in the object that is supposed to receive the copy). In your case

MyClass MyObj(MyClass(1, 2));

can be transformed into

MyClass MyObj(1, 2);

even if the copy constructor has side-effects.

This process is called elision of copy operation. It is described in 12.8/15 in the language standard.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Copying a comment from [this duplicated question](https://stackoverflow.com/q/47960154/5825294): _[...] this happens whenever a temporary object is created for the sole purpose of being copied and subsequently destroyed (copy-ellision). **But this sounds exactly like the intended use case for move constructor?**_. – Enlico Apr 22 '19 at 18:05
  • When you say _for the sole purpose of being copied and subsequently destroyed_, do you also mean that the other constructor (in this case the one which takes two `int` parameters) must not have side effects? – Enlico May 04 '19 at 09:01
19

The copy constructor may be elided in such a case.

Likewise with MyClass MyObj = MyClass( 1, 2 );.

And with

std::string str = "hello";

Such code has an implicit constructor call to convert the char* to a std::string.

std::string str = std::string( "hello" ); // same, written more verbosely

Without copy elision, the "easy" string initialization by assignment syntax would incur an additional deep copy. And that syntax is 99% equivalent to what you have.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
5

Apart from what Potatoswatter and Andrey T. has said, note that you can coax most compilers not to elide constructors. GCC typically provides you with -fno-elide-constructors and MSVC with /Od which should give you the desired output. Here's some code:

#include <iostream>

#define LOG() std::cout << __PRETTY_FUNCTION__ << std::endl // change to __FUNCSIG__ on MSVC > 2003

class MyClass
{
public:
  ~MyClass() { LOG(); }
  MyClass():x(0), y(0){LOG(); } //default constructor
  MyClass(int X, int Y):x(X), y(Y){LOG(); } //user-defined constructor
  MyClass(const MyClass& tempObj):x(tempObj.x), y(tempObj.y){LOG(); } //copy constructor

private:
int x; int y;
};

int main()
{
 MyClass MyObj(MyClass(1, 2)); //User-defined constructor was called.
 MyClass MyObj2(MyObj); //Copy constructor was called.
}

Compiled with GCC 4.5.0 on MingW32:

 g++ -Wall -pedantic -ansi -pedantic tmp.cpp -o tmp -fno-elide-constructors

Output:

$ tmp.exe
MyClass::MyClass(int, int)
MyClass::MyClass(const MyClass&)
MyClass::~MyClass()
MyClass::MyClass(const MyClass&)
MyClass::~MyClass()
MyClass::~MyClass()
dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • While posible, you probably shouldn't do this without really good reason. People write code assuming elision will take place, because it is an extremely common optimization. And superfluous copies can be a *huge* performance penalty. Just don't ever write code that depends on side effects in a copy constructor, and let the compiler do what it do. – Dennis Zickefoose Sep 08 '10 at 01:57
2

What makes you think it's not invoked? Try this [Edit: changing code to use private copy ctor, since availability has to be checked even if use of the copy ctor is elided]:

class MyClass
{
public:
   ~MyClass() {}
   MyClass():x(0), y(0){} //default constructor
   MyClass(int X, int Y):x(X), y(Y){} //user-defined constructor

private:
   MyClass(const MyClass& tempObj):x(tempObj.x), y(tempObj.y){} //copy constructor
   int x; int y;
};

int main()
{
    MyClass MyObj(MyClass(1, 2)); //User-defined constructor was called.
    MyClass MyObj2(MyObj); //Copy constructor was called.
}

Attempting to compile this gives errors for both lines in main:

myclass.cpp(17) : error C2248: 'MyClass::MyClass' : cannot access private member
 declared in class 'MyClass'
        myclass.cpp(11) : see declaration of 'MyClass::MyClass'
        myclass.cpp(4) : see declaration of 'MyClass'
myclass.cpp(18) : error C2248: 'MyClass::MyClass' : cannot access private member
 declared in class 'MyClass'
        myclass.cpp(11) : see declaration of 'MyClass::MyClass'
        myclass.cpp(4) : see declaration of 'MyClass'

Conceptually, the copy ctor is used in both cases, and the compiler is obliged to check that it's accessible. In the first case, however, the compiler is free to elide actual use of the copy ctor, as long as it would be able to use it.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111