Search code examples
c++initialization

Assignment at Initialization without Default Constructor


In this program, I completely understand why the first part of the main function fails and needs to be commented - there's no implicit default ctor after I've implemented the value ctor within TestingClass. Perfectly logical. However, I was a bit surprised to find that the second part (creation of test2 object) succeeds just fine, at least with gcc 4.8.4.

#include <iostream>
using namespace std;

class TestingClass
{
  public:
    TestingClass(int inVal)
    {
      val = inVal;
    }

    int val;
};

TestingClass testingCreator()
{
  return TestingClass(100);
}

int main()
{
  /*
  TestingClass test1;
  test1 = testingCreator();
  cout << "Test1: " << test1.val << endl;
  */

  TestingClass test2 = testingCreator();
  cout << "Test2: " << test2.val << endl;
}

Thinking about it, it also makes sense, because the object, test2, will never have existed without having been constructed / initialized, but most people think of initialization in this way as just being a declaration and an assignment on one line. Clearly, though, initialization is more special than that, since this code works.

Is this standard C++? Is it guaranteed to work across compilers? I'm interested in how initialization in this way is different than just declare (using a default ctor) and then assign (via a temporary object created in the global function).

UPDATE: Added a copy ctor and a third case that clearly uses the copy ctor.

#include <iostream>
using namespace std;

class TestingClass
{
  public:
    TestingClass(const TestingClass &rhs)
    {
      cout << "In copy ctor" << endl;
      this->val = rhs.val + 100;
    }
    TestingClass(int inVal)
    {
      val = inVal;
    }

    int val;
};

TestingClass testingCreator()
{
  return TestingClass(100);
}

int main()
{
  /*
  TestingClass test1;
  test1 = testingCreator();
  cout << "Test1: " << test1.val << endl;
  */

  TestingClass test2 = testingCreator();
  cout << "Test2: " << test2.val << endl;

  TestingClass test3(test2);
  cout << "Test3: " << test3.val << endl;
}

This outputs:

Test2: 100
In copy ctor
Test3: 200

UPDATE 2: Its been a long time, but happened across this old question of mine, and thought I'd add this in case others end up here. As described in the answer below, the copy constructor IS getting used, but the actual call to the copy ctor was elided, so the "side effects" of the copy ctor (specifically the printing of "In copy ctor" as well as the modification of the value by adding 100) are not performed. To prove this, you can specify the g++ command line argument -fno-elide-constructors and it will prevent the elision from happening. When compiling with that flag, the output of the example is:

In copy ctor
In copy ctor
Test2: 300
In copy ctor
Test3: 400

You probably don't want to use that flag in general, but it may be useful when trying to understand what is happening behind the scenes with constructor elision. As many references on the topic point out, this is clearly a reason why your copy ctor should do nothing other than copy - any other side effects may not be performed if constructor elision is used. In my example, I had two side effects in the copy ctor, and they were not performed during execution, so avoiding the side effects in the copy ctor is excellent advice since the results could be surprising to some.


Solution

  • Your thinking on what TestingClass test2 = testingCreator(); does is flawed. When you see

    type name = stuff;
    

    You do not create name and then assign to it stuff. What you do is copy initialize name from stuff. This means you call the copy or move constructor. Generally this call can be elided by optimizing compilers but if it was not then that is what you would see. In either case the default constructor is never called.

    In your first example

    TestingClass test1;
    

    Forces the default constructor to be called and since you do not have one you get an error.