2

I wrote a simple program to learn more about the order of creating and destructing objects in C++ (using Visual Studio 2015). Here it is:

#include <iostream>
#include <string>

using namespace std;

class A
{
public:
    A(string name)
        : name(name)
    {
        cout << "A(" << name << ")::constructor()" << endl;
    }
    ~A()
    {
        cout << "A(" << name << ")::destructor()" << endl;
    }
private:
    string name;
};

class C
{
public:
    C(string name, A a)
        : name(name), a(a)
    {
        cout << "C(" << name << ")::constructor()" << endl;
    }
    ~C()
    {
        cout << "C(" << name << ")::destructor()" << endl;
    }
private:
    string name;
    A a;
};

class B
{
public:
    B(string name)
        : name(name)
    {
        cout << "B(" << name << ")::constructor()" << endl;
    }
    ~B()
    {
        cout << "B(" << name << ")::destructor()" << endl;
    }
private:
    string name;
    A a1{"a1"};
    A a2{"a2"};
    C c1{"c1", a1};
    A a3{"a3"};
};

int main()
{
    B b("b1");
    return 0;
}

The output surprised me a little bit (the a1s):

A(a1)::constructor()
A(a2)::constructor()
C(c1)::constructor()
A(a1)::destructor()
A(a3)::constructor()
B(b1)::constructor()
B(b1)::destructor()
A(a3)::destructor()
C(c1)::destructor()
A(a1)::destructor()
A(a2)::destructor()
A(a1)::destructor()

To learn more about what was going on I added information about the instances of objects:

    A(string name)
        : name(name)
    {
        cout << "A(" << name << ")::constructor(), this = " << this << endl;
    }
    ~A()
    {
        cout << "A(" << name << ")::destructor(), this = " << this << endl;
    }

The result was even more surprising:

A(a1)::constructor(), this = 0039FB28
A(a2)::constructor(), this = 0039FB44
C(c1)::constructor()
A(a1)::destructor(), this = 0039F8A8
A(a3)::constructor(), this = 0039FB98
B(b1)::constructor()
B(b1)::destructor()
A(a3)::destructor(), this = 0039FB98
C(c1)::destructor()
A(a1)::destructor(), this = 0039FB7C
A(a2)::destructor(), this = 0039FB44
A(a1)::destructor(), this = 0039FB28

Namely, why is a1's constructor only called once and destructor 3 times? I'm passing a by value so obviously at least 1 temporary object is created but please explain to me when and how many A instances are created and destroyed?

NPS
  • 6,003
  • 11
  • 53
  • 90
  • 5
    You've not annotated many of the other constructors of your class, so you're only seeing a partial picture. – Kerrek SB Feb 20 '16 at 13:09
  • @KerrekSB Are you referring to printing out of `this` values for classes B and C? Or what? – NPS Feb 20 '16 at 13:10
  • The compiler is adding some default constructors that you can't see. – Galik Feb 20 '16 at 13:12
  • 4
    Notably, the copy constructors – Margaret Bloom Feb 20 '16 at 13:13
  • 1
    @Galik: No conforming compiler would do such a nasty thing. Compilers don't generally make stuff up; they follow the rules of the language. – Kerrek SB Feb 20 '16 at 13:13
  • Please let me know what code to add to see the full picture then. – NPS Feb 20 '16 at 13:13
  • 2
    @NSP Compiler indeed adds copy constructor. Try to define it yourself as `A(const A& other) {...}` with print statements and you'll see whats going on. – vim Feb 20 '16 at 13:16
  • @πάνταῥεῖ Then what is constructing all the copies? – Galik Feb 20 '16 at 13:20
  • @πάνταῥεῖ [tag:c++] is one of your top tags, are we missing something? – Margaret Bloom Feb 20 '16 at 13:33
  • @MargaretBloom Oops, seems I've been [missing something](http://stackoverflow.com/a/2380766/1413395). Even experts have bad days :-P ... _@Kerrek_ was pointing to _default constructors_, which certainly won't be compiler generated for such case. – πάντα ῥεῖ Feb 20 '16 at 13:37

1 Answers1

5

As already noted in the comments, objects of type A are also constructed via copy-construction when you pass them as arguments by value. In order to see this you can add a copy-constructor on your own:

A(const A& other)
: name(other.name)
{
    cout << "A(" << name << ")::copy-constructor(), this = " << this << endl;
}

Sample output:

A(a1)::constructor(), this = 0xbff3512c
A(a2)::constructor(), this = 0xbff35130
A(a1)::copy-constructor(), this = 0xbff350e8
A(a1)::copy-constructor(), this = 0xbff35138
C(c1)::constructor()
A(a1)::destructor(), this = 0xbff350e8
A(a3)::constructor(), this = 0xbff3513c
B(b1)::constructor()
B(b1)::destructor()
A(a3)::destructor(), this = 0xbff3513c
C(c1)::destructor()
A(a1)::destructor(), this = 0xbff35138
A(a2)::destructor(), this = 0xbff35130
A(a1)::destructor(), this = 0xbff3512c

Try it online

As you can see, one copy-construction happens when you pass a1 as parameter to the constructor of c1 and a second one happens when this constructor initializes its member a. The temporary copy is destructed immediately afterwards while the member is destructed when c is destructed.

Edit:
Here you can read the exact rules when a copy-constructor is created.
In order to not create a default copy-constructor it is not sufficient to provide any user-defined constructor, it needs to be a copy/move-constructor.

Edit2:

Taken from C++14 standard (12.8 Copying and moving class objects):

7 If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.

Anedar
  • 4,235
  • 1
  • 23
  • 41