Help me to understand constructors and destructors

Here is code that I'm trying to understand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;

class Student;

class Person {
public:
	Person() { cout << "constructor Person" << endl; }
	~Person() { cout << "destructor Person" << endl; }
};

const Student& returnPerson(const Student& p) { return p; }

class Student :public Person {
public:
	Student() { cout << "constructor Student" << endl; }
	~Student() { cout << "destructor Student" << endl; }
};

Student returnStudent(Student s) { return s; }

class PhDStudent : public Student {
public:
	PhDStudent() { cout << "constructor PhDStudent" << endl; }
	~PhDStudent() { cout << "destructor PhDstudent" << endl; }
};

Student returnPhDStudent(Student s) { return s; }

int main(int argc, char* argv[]) {
	PhDStudent james;
	returnPhDStudent(returnStudent(returnPerson(james)));
}



This is output of that code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
constructor Person
constructor Student
constructor PhDStudent
destructor Student
destructor Person
destructor Student
destructor Person
destructor Student
destructor Person
destructor Student
destructor Person
destructor PhDstudent
destructor Student
destructor Person


Now I understand that line PhDStudent james; make constructors in order that it makes and call last 3 destructors in reverse order,
but I don't understand how line returnPhDStudent(returnStudent(returnPerson(james))); call all this destructors.

Can someone help me?

Edit:
Also I run this code on this site and this is output that I get
1
2
3
4
5
6
7
8
9
10
11
12
constructor Person
constructor Student
constructor PhDStudent
destructor Student
destructor Person
destructor Student
destructor Person
destructor Student
destructor Person
destructor PhDstudent
destructor Student
destructor Person


Why is there difference?
Last edited on
Lines 1,2,3 of the output are the result of line 31. No surprise.
Lines 12,13,14 are the result of james going out of scope when the program exits.
That leaves lines 4-11 in which we see the student and person destructor called 4 times.

Line 12: You're passing and returning student by reference. Therefore no constructor or destructor is called.
Lines 20,28: You're passing and returning student by value. Therefore the compiler makes a copies of student to push on the stack. Since you don't provide a copy constructor, the compile provides one (without a cout statement). When the copy of student is popped off the stack, the standard destructor is called.

Let's take apart line 32. The first thing to execute is returnPerson(james).
james is passed and returned by reference leaving a reference on the stack. No constructors or destructors called.

Next, returnStudent() is called. The compile now calls student's copy constructor to convert the reference left on the stack into an instance that is passed by value. A copy of student is then returned by value and left on the stack. When this value is popped off the stack, student and persons destructors get called.

Finally returnPhDStudent() is called. The above process repeats since student is again passed by value;.
And the difference in output is presumably because the first output was from a version of the program that didn't use references on one of the functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
using namespace std;

class Person {
public:
     Person() { cout <<  "Person\n"; }
     Person(const Person&) { cout << "Person (copy)\n"; }
    ~Person() { cout << "~Person\n"; }
};

class Student : public Person {
public:
     Student() { cout <<  "Student\n"; }
     Student(const Student&) : Person() { cout << "Student (copy)\n"; }
    ~Student() { cout << "~Student\n"; }
};

class PhDStudent : public Student {
public:
     PhDStudent() { cout <<  "PhDStudent\n"; }
     PhDStudent(const PhDStudent&) : Student() { cout << "PhDStudent (copy)\n"; }
    ~PhDStudent() { cout << "~PhDstudent\n"; }
};

#define FUNCS 3

#if FUNCS == 1
Student& returnPerson    (Student& s) { return s; }
Student  returnStudent   (Student  s) { return s; }
Student  returnPhDStudent(Student  s) { return s; }
#elif FUNCS == 2
Student& returnPerson    (Student& s) { return s; }
Student& returnStudent   (Student& s) { return s; }
Student& returnPhDStudent(Student& s) { return s; }
#else
Student  returnPerson    (Student  s) { return s; }
Student  returnStudent   (Student  s) { return s; }
Student  returnPhDStudent(Student  s) { return s; }
#endif

int main() {
    PhDStudent james;
    returnPhDStudent(returnStudent(returnPerson(james)));
}

Output with FUNCS == 3:

Person
Student
PhDStudent
Person
Student (copy)
Person
Student (copy)
Person
Student (copy)
Person
Student (copy)
~Student
~Person
~Student
~Person
~Student
~Person
~Student
~Person
~PhDstudent
~Student
~Person

Last edited on
@AbstractionAnon thank you very much, your explanation why only destructors are called is very clear and easy to understand

@dutch I tried code that you modified and here what I got
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Person
Student
PhDStudent
Person
Student (copy)
Person
Student (copy)
~Student
~Person
Person
Student (copy)
~Student
~Person
Person
Student (copy)
~Student
~Person
~Student
~Person
~PhDstudent
~Student
~Person


Is this normal thing that different compilers get different results? Or should I try to write program in the way that prevent this to happen? Is there literature that I can use that teach how to do that?

Edit:
I now saw that output is same but in little bit different order.
Last edited on
Topic archived. No new replies allowed.