sequence of the constructor of two parent classes

Viewed 95

I have one question on the two version of code. The only different is to switch the virtual keyword between the parents class. Is there any reason why this happened?

Version one:

#include<iostream> 
using namespace std; 
class Person { 
public: 
    Person(int x) { cout << "Person::Person(int ) called" << endl; } 
    Person()     { cout << "Person::Person() called" << endl; } 
}; 

class Faculty : public Person { 
public: 
    Faculty(int x):Person(x) { 
    cout<<"Faculty::Faculty(int ) called"<< endl; 
    } 
}; 

class Student : virtual public Person { 
public: 
    Student(int x):Person(x) { 
        cout<<"Student::Student(int ) called"<< endl; 
    } 
}; 

class TA : public Faculty, public Student { 
public: 
    TA(int x):Student(x), Faculty(x), Person(x) { 
        cout<<"TA::TA(int ) called"<< endl; 
    } 
}; 

int main() { 
    TA ta1(30); 
} 

The output of Version: Person::Person(int ) called
Person::Person(int ) called
Faculty::Faculty(int ) called
Student::Student(int ) called
TA::TA(int ) called

Version two:

#include<iostream> 
using namespace std; 
class Person { 
public: 
    Person(int x) { cout << "Person::Person(int ) called" << endl; } 
    Person()     { cout << "Person::Person() called" << endl; } 
}; 

class Faculty : virtual public Person { 
public: 
    Faculty(int x):Person(x) { 
    cout<<"Faculty::Faculty(int ) called"<< endl; 
    } 
}; 

class Student :  public Person { 
public: 
    Student(int x):Person(x) { 
        cout<<"Student::Student(int ) called"<< endl; 
    } 
}; 

class TA : public Faculty, public Student { 
public: 
    TA(int x):Student(x), Faculty(x), Person(x) { 
        cout<<"TA::TA(int ) called"<< endl; 
    } 
}; 

int main() { 
    TA ta1(30); 
} 

The output is: Person::Person(int ) called
Faculty::Faculty(int ) called
Person::Person(int ) called
Student::Student(int ) called
TA::TA(int ) called

3 Answers

The order that classes are initialized is based on the order they're declared in your class's base class specifier list:

  1. If this is the most-derived class, virtual base classes are initialized. Their initialization order is based on a depth-first left-to-right search of this class's base class specifier list.
  2. Non-virtual base classes are initialized from left to right
  3. This class's members are constructed based on declaration order
  4. This class's constructor body runs

This process repeats recursively for each object initialized.


For the first example:

  1. TA is the most-derived class, so its Person virtual base object is initialized first.
    • Person's constructor body runs and prints its message.
  2. Faculty is the first declared base class, so it's initialized next.
    • Faculty has a non-virtual base class Person, so it initializes its own Person sub-object.
      • Person's constructor body runs and prints its message
    • Faculty's constructor body runs and prints its message
  3. Student is the next base class, so it's initialized next.
    • Student is not the most-derived class, so it doesn't get its own Person sub-object to initialize.
    • Student's constructor body runs and prints its message
  4. TA's constructor body runs and prints its message

The result is that the constructors' bodies are executed in the following order:

  1. Person
  2. Person
  3. Faculty
  4. Student
  5. TA

For the second example:

  1. TA is the most-derived class, so its Person virtual base object is initialized first.
    • Person's constructor body runs and prints its message.
  2. Faculty is the first declared base class, so it's initialized next.
    • Faculty is not the most-derived class, so it doesn't get its own Person sub-object to initialize.
    • Faculty's constructor body runs and prints its message
  3. Student is the next base class, so it's initialized next.
    • Student has a non-virtual base class Person, so it initializes its own Person sub-object.
      • Person's constructor body runs and prints its message
    • Student's constructor body runs and prints its message
  4. TA's constructor body runs and prints its message

The result is that the constructors' bodies are executed in the following order:

  1. Person
  2. Faculty
  3. Person
  4. Student
  5. TA

Note that in both cases, there are two Person subobjects. For Faculty and Student to share a single Person subobject, both would have to inherit virutally from Person i.e.:

class Person {
public:
    Person(int x) { cout << "Person::Person(int) called" << endl; }
    Person()      { cout << "Person::Person() called" << endl; }
};

class Faculty : virtual public Person {
public:
    Faculty(int x) : Person(x) {
        cout<<"Faculty::Faculty(int) called"<< endl;
    }
};

class Student : virtual public Person {
public:
    Student(int x) : Person(x) {
        cout<<"Student::Student(int) called"<< endl;
    }
};

class TA : public Faculty, public Student {
public:
    TA(int x) : Student(x), Faculty(x), Person(x) {
        cout<<"TA::TA(int) called"<< endl;
    }
};

In this case the logic would be:

  1. TA is the most-derived class, so its Person virtual base object is initialized first.
    • Person's constructor body runs and prints its message.
  2. Faculty is the first declared base class, so it's initialized next.
    • Faculty is not the most-derived class, so it doesn't get its own Person sub-object to initialize.
    • Faculty's constructor body runs and prints its message
  3. Student is the next base class, so it's initialized next.
    • Student is not the most-derived class, so it doesn't get its own Person sub-object to initialize.
    • Student's constructor body runs and prints its message
  4. TA's constructor body runs and prints its message

Resulting in the class' constructor bodies executing in the following order:

  1. Person
  2. Faculty
  3. Student
  4. TA

The order of the initialization is set according to left to right for the inherited base classes. It basically ignores the order you have set in the constructor list.

class TA : public Faculty, public Student { 
public: 
    TA(int x):Student(x), Faculty(x) { 
        cout<<"TA::TA(int ) called"<< endl; 
    } 
}; 

Will initialize for the first case as:

class TA : public Faculty, public Student { 
public: 
    TA(int x):Faculty(x), Student(x) { 
        cout<<"TA::TA(int ) called"<< endl; 
    } 
};

But virtual base classes are initialized firstly, so we have:

class TA : public Faculty, public Student { 
public: 
    TA(int x):Person(x), Faculty(x), Student(x) { 
        cout<<"TA::TA(int ) called"<< endl; 
    } 
};

Now, when Faculty(x) is initialized, Person(x) is initialized (since it derives from it). Then Student it initialized, and lastly, the TA-object itself.

You can see the incorrectness in the initializer list by compiling with -Wall.

First of all, to implement "diamond" multiple inheritance in correct way, both your classes Faculty and Student must be virtually inherited from Person. In this case you'll see only one call to Person's constructor in your output. And this is the purpose: to call grandparent's constructor only once.

You probably use very non-restrictive compiler, because restrictive compilation of your code (using VS2017, for example) immediately reports an error: error C2385: ambiguous access of 'Person' . And this is the expected behavior.

Additional Explanation: using virtual inheritance postpones call to inheriting class constructor to be performed by it's "grandchild". In your case, class TA is grandchild of Person and only it calls Person's constructor, while Faculty and Student don't.

Related