2

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.

1
  • "but most people think of initialization in this way as just being a declaration and an assignment on one line" ... not most C++ programmers. (Hopefully not any at all!) Commented Sep 27, 2016 at 22:45

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Interesting - I'm well aware of the copy ctor, that just doesn't "look" like a copy ctor call - I usually think of that as when passing by value or when doing the more obvious "TestingClass test2(test3);" kind of thing. Great quick answer - thanks!
But - I just updated the code to explicitly implement a copy ctor and added a print statement in there. When I execute the program its not printed.. However, if I add a third test "TestingClass test3(test2);", the string IS printed. Further, I set "this->val = rhs.val + 100;" and test2.val is still printed as 100, not 200, whereas test3's value is printed as 200.
@daroo As I said compilers are good at optimizing this and even though there is a side effect the copy is elided and the only constructor that is called is TestingClass(int inVal)
@daroo learning from a book will save you a lot of time over learning by trial and error
@M.M I'm not using trial and error, I was trying to verify the answers that the copy ctor performs this operation. Since I didn't see the effects of the explicit copy ctor, this seems like a perfectly valid clarification. The books and websites I've looked in don't seem to address this in the detail I was looking for - maybe I'm looking in the wrong books.
|
0

test2 is defined by the copy constructor of TestingClass, taking the result of testingCreator as argument. The copy constructor TestingClass::TestingClass(const TestingClass&) is automatically generated by the compiler and the C++ standard guarantees that it copies the val field.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.