Search code examples
inheritancemultiple-inheritancevirtual-inheritancediamond-problem

Virtual class in diamond inheritance


From my understanding, using virtual suppresses the constructor of the base class, and therefore is able to avoid multiple objects of the base class being instantiated.

If the constructor of both the derived class is suppressed, how is that one instance of the base class (from which the virtual classes are derived) instantiated?

Also if the constructor of the base class is suppressed, when I have a new class that has to inherit from one of the virtual classes, will the base class's constructor be suppressed there too?

I have explained my question below with example.

class student{ 
    int rollNo;
    public : student(int a): rollNo(a) { }
    int getRollNo() {
        return rollNo;
    }
};
class midsem : virtual public student{
    float midSemMarks;
    public : midsem(int a, float b) : student(a), midSemMarks(b) { }
    float getMidSemMarks() {
        return midSemMarks;
    }
};

class endsem : virtual public student{
    float endSemMarks;
    public : endsem(int a, float b):student(a),endSemMarks(b) { }
    float getEndSemMarks () {
        return endSemMarks;
    }
};
class total : public midsem, public endsem{
    float totalMarks;
    public:total(int a, float b, float c) :student(a), midsem(a,b), endsem(a,c) { }
    float getTotal() {
        return midsem::getMidSemMarks() + endsem::getEndSemMarks();
    }
  1. In the above example, when an object of total is created, a single object of student is instantiated. But if using virtual supresses the constructor of student in both endsem and midsem, how is that one instance of student created when an object of total is created?

  2. Now when I want to create another class that derives from endsem, will creating an object of the new class invoke the constructor of student class, as using virtual is supposed to suppress it?


Solution

  • From my understanding, using virtual suppresses the constructor of the base class, and therefore is able to avoid multiple objects of the base class being instantiated.

    Virtual applies to the inheritance relation; it suppresses the invocation of the virtual base class constructor for any intermediate derived class, whether that base is named explicitly in the ctor-init-list or not (in which case it means you would have invoked the default constructor of that base without that rule).

    If the constructor of both the derived class is suppressed, how is that one instance of the base class (from which the virtual classes are derived) instantiated?

    Virtual bases are initialized from the most derived class, not an intermediate class, whether they are mentioned explicitly in that class ctor-init-list or not.

    As usual not mentioning a base class means that the default constructor is invoked.

    It implies that these members, the constructors must be accessible, as non static members, from the most derived class.

    Non static members are only accessible in the derived class if the base class is accessible via a valid accessible conversion, so:

    1) Private inheritance of virtual bases can make code ill-formed:

    class PrivateBase {};
    class Der : virtual PrivateBase {};
    class Der2 : Der {};
    

    (Remember that with the keyword class members and also bases are private by default.)

    Implicitly declared special members (constructors, destructor, assignment) follow the usual rules and their definition must be valid as if they were explicitly written; so for Der2 to be valid, the Der2(); definition of a default constructor and the Der2::Der2 () {} would have to be valid.

    So Der () {} actually means Der () : PrivateBase () {} (which is valid) but Der2 () : PrivateBase(), Der() {} which is ill-formed.

    2) Because of the multiple paths of access inherent to virtual inheritance, and because there is a need of only of accessible path, virtual private inheritance almost never prevents a class from being derived from:

    class PrivateBase {};
    class Der : virtual PrivateBase {};
    class Der2bis : Der, virtual PrivateBase {};
    

    This Der2bis variant is almost like Der2 except that the virtual base is inherited once more, which makes almost zero semantic difference (layout of some data structures may change), except for the fact the virtual PrivateBase base of Der2bis is now accessible directly no accessibility of the Der2bis -> PrivateBase derived-to-base conversion from the class members is a given (the conversion is still not accessible from other code).

    3) It implies that private constructors can be used only if the most derived class is made a friend of the base. It implies that some classes could be derived from, but wouldn't have valid constructor definitions then:

    class TheFriend;
    
    class AllPrivate {
      AllPrivate ();
      AllPrivate (AllPrivate&);
      friend class TheFriend;
    };
    
    class TheFriend : virtual AllPrivate { };
    

    Note that TheFriend can only default construct its base class subobject (by naming its default constructor) by virtue of being its friend.

    But deriving further will fail, even with the repetition of the private base to make it accessible:

    class MoreDerived : TheFriend, virtual AllPrivate {};
    

    Here the private base is made accessible but its members are still not accessible, MoreDerived is not a friend, and it cannot call base constructors, so none of its constructors, whether implicitly or explicitly defined, can be well-formed.