Multiple Inheritance

6. Multiple Inheritance
in

Introduction

Multiple inheritance (MI) is different from multi-level inheritance (nested generalization/inheritance). In multiple inheritance, a class derives from more than one class. There are situations where multiple inheritance is combined with nested generalization. Representing various kinds of windows is one of the many classic examples for multiple inheritance. A window can be horizontally scrollable and/or vertically scrollable, can be editable and/or non-editable window. So, depending on the property of the specialized window, it can derive from more than one specialized window.

One more example from C++ Standard library is, iostream class. It derives from istream class and ostream class, both of which derive from ios class.

In C++, the general rule while constructing a concrete class is, object will be constructed from the top of the class hierarchy. Each class's constructor will call its immediate base class's constructor from left to right order of derivation and then executes its own constructor, which involves in adding virtual table pointer, constructing its own members, in the order they are specified in class definition. This rule does not hold good when there comes virtual base class in the class hierarchy.

If no constructor is specified in the initialization list, compiler will insert code for calling default constructor, if its there, else it will give a compilation error. So, it's the derived class's responsibility to call proper constructor for it's base class as well as for it's members in the initialization list.

Assuming a class hierarchy where class Derived, derives from BaseA and BaseB classes, then the object layout of Derived will be like this:

multiple1.png

"this" pointer alignment

If the class definition of above class is something like this:

class BaseA {
   int iMemA;
public:
   void FunctionA(int );
};

class BaseB {
   int iMemB;
public:
   void FunctionB(int );
};

class Derived : public BaseA, public BaseB {
   int iMem;
};

We can create object of Derived using BaseA pointer as well as BaseB pointer. But we can call only FunctionA with BaseA pointer and only FunctionB with BaseB pointer. Compiler will take care of this. And when we create Derived using BaseB, compiler will make sure that BaseB pointer is pointing to BaseB part of Derived and not at the beginning of the object.

Derived* pD = new Derived;
BaseA* pA = pD;
//Here pA should be same as pD, as it points to BaseA part of Derived.
if( pA != pA ) {
    cout<<”Impossible”;
    return -1;
}
pA->FunctionA(1); //OK
pA->FunctionB(2); //ERROR
BaseB* pB = pD;
//Here pB should not be same as pA, as it points to BaseB part of Derived.
if( pB == pA ) {
   cout<<”Impossible”;
   return -1;
}
pB->FunctionA(2); //ERROR
pB->FunctionB(1); //OK
//One can call both functions using pointer to Derived
pD->FunctionA(4); //OK
pD->FunctionB(5); //OK

And the reason for this is, “this” pointer alignment. When a Derived pointer is assigned to one of its Base pointer, pointer will align to its offset with in the object layout. Compiler will do this alignment for us implicitly and we don't have to worry about that. We can see the actual offset with in the object layout with below code:

Derived* pD = NULL;
BaseA* pA = pD; //Both will be 0
BaseB* pB = pD; // pB may not be Null

pB may not be null now, it will be added with the offset with in pD. Typically it will be pB = pD + sizeof all base those comes before BaseB with in Derived object layout. In our case its sizeof(BaseA) which is 4 bytes.

Some smart compilers will take care of this problem with an extra condition before doing assignment. It will check for NULL, if so, assign it with NULL, else add the offset and assign.

pB = (pD == 0) ? 0 : (pD + offset(BaseB));

This alignment of pointer happens when we call member function of Base class using derived pointer. Its because, Base class function expects “this” pointer to be of type Base only. So, here also it should be aligned properly while calling function and sending the object pointer as “this” pointer. This is required because, we may access Base member data with in Base function. So it should be a valid base pointer, not derive pointer.

pD->FunctionB(7);

will be converted into something like this by compiler:

FunctionB_BaseB(pD+4, 7);

Again if we try to delete Derived object using Base pointer, it may crash. For example, below code will CRASH.

Derived* pD = new Derived;
BaseB* pB = pD;
delete pB; // This will CRASH

But in above case, deleting with pD will be successful and with pA, it wont CRASH, but it wont call the Derived destructor.

So, the solution for all above problem is to make the destructors of Base as virtual. In that case, we can delete Derived object using any of the Base pointer. And the virtual mechanism makes sure that, it will be the derived class destructor, which will be invoked first, and whole object will be deleted.

Virtual Inheritance

As I have mentioned earlier, iostream class of standard C++ library inherits from istream and ostream class. Both istream and ostream derives from class ios. So this class hierarchy will form a diamond.

The diamond problem refers to a class hierarchy in which a particular base class appears more than once in a class inheritance hierarchy from two different path. If that base class is a large object, then the duplication of base class will be a storage overhead and it may lead to a state where in the two copies of the base instance might get modified separately or inconsistently. And it may cause ambiguity while accessing any members of that duplicated base class.

In C++, the solution for this problem is through “virtual inheritance” which really means that “sharing common base class”. As this one is implemented using virtual keyword while showing inheritance relationship, it's generally called as virtual inheritance.

class ios { ... };
// istream and ostream inherit ios virtually, so ios is VBC (virtual base class)
class istream : public virtual ios { ... };
class ostream : public virtual ios { ... };
//iostream inherits from istream and ostream with single copy of ios
class iostream : public istream, public ostream { ... };

A class, which is been inherited virtually, is called as Virtual Base Class (VBC) and there will be only one copy of this VBC in the concrete class. This VBC behaves exactly like a non-virtual base class and the only difference is, there will be a unique object in derived class.

Virtual Inheritance (VI) is the more expensive technique compare to Single Inheritance (SI) and Multiple Inheritance (MI). In case of SI, embedded base class and derived class (derived classes, if in case of multi-leveled) share a common address point. That is there won't be “this” pointer alignment. In case of MI, left most base class inherited and the derived class share common address point, but for other embedded base, it will be some constant displacement (offset). But with VI, in general there won't be a fixed displacement from the address point of the derived class to its virtual base. And, its not constant if that class is derived from some other class. Object layout of such class hierarchy is floating and it requires some information on VBC as well. All these add more overheads during compilation and runtime.

Object Layout

Consider the class hierarchy, in which Derived inherits from BaseA, BaseB, and BaseC:

multiple2.png

Lets consider 3 different scenarios:

  1. Assuming all class has some virtual functions
  2. Assuming no virtual functions, but all Base are derived virtually
  3. Assuming virtual functions in all class and all Base are derived virtually

multiple3.png

As shown in the above object layout of Derived, in case of VI, embedded objects of VBC comes after the Derived class. And it also adds virtual base pointer. And adding new virtual functions and overriding base's virtual functions and adding more class in the hierarchy by deriving from above classes will add more complexity in the object layout.

In scenario 3, if Derived class does not have any new virtual functions (that is only overriding base class virtual functions) then the first vptr for Derived will be eliminated. Notice that, none of the embedded VBC objects share the common address point with derived class.

Lets consider one more example of Diamond Hierarchy as below:

multiple4.png

Assuming Base has some virtual functions which is been overridden by DerivedA and DerivedB, then the object layout of Base, DeriveA, DerivedB, and Join will be as below:

multiple5.png

The general rule for object layout is as below:

  • If there is any direct non-virtual base class, it will be constructed first in the same order they are derived, that if from left to right. So, these embedded objects start first in the object layout.
  • If this class has additional virtual functions which are different from base class virtual functions, then place a virtual pointer if in case no vptr is created for this class yet. If a vptr is already there, then only VTABLE will be updated. This rule is required as all VBCs comes last in the object layout and there should be one vptr at the start of object layout if this class has additional virtual functions
  • If this class has VBC, keep a virtual base pointer, if it's not inherited yet.
  • Keep all new member data in the order they are declared in the class definition.
  • Keep a single copy of all VBC in the reverse order of their construction. VBCs are constructed before constructing any non-virtual base class in the order of depth first and left-to-right.

Order of Construction and destruction

When we instantiate a class which is part of class hierarchy, constructors are called in some fixed order as below:

  • If it's “most derived” class, initialize virtual base pointer entries and call constructors of all VBCs from depth first, left to right order. (vbptr)
  • Call constructors of direct non-virtual bases
  • Call constructors of data members in the order they have been declared in class definition
  • Initialize virtual function pointer entry (vptr)
  • Perform user specific code in the body of constructor.

During destruction, above actions happen in the reverse order. To avoid calling VBC''s constructor multiple times from different path, that is, to call VBC''s constructor only once, each class's constructor with VBC receives a hidden “most derived” flag to indicate whether to call VBC''s constructor or not.

While initializing any object of class hierarchy, initialization list of most-derived class's constructor directly invokes the VBC''s constructor. C++ rule says that VBCs are constructed before all non-VBC. So we should take extra care to initialize VBC with extra parameters needed at most derived class's constructor.

“Delegate to a sister class” via virtual inheritance

This can be defined as “A class which inherits VBC knows nothing about (other class which also inherits from VBC) will supply the override of a virtual function of VBC”.

class Base {
public:
   virtual void DisplayA(int)=0;
   virtual void DisplayB(int)=0;
   ...
};

class DerivedA : public virtual Base {
public:
   void DisplayA(int aVar) {
       cout<<”DerivedA”<<endl;
       //this invokes DerivedB::DisplayB()
       if(aVar)
           DisplayB(0);
    }
    ...
};

class DerivedB : public virtual Base {
public:
   void DisplayB() {
       cout<<”DerivedB”<<endl;
       //this invokes DerivedA::DisplayA()
       if(aVar)
           DisplayA(0);
    }
};

class Join : public DerivedA, public DerivedB {
...
};

If in the above class hierarchy, we can instantiate only Join class but not DerivedA and DeriveB. And if the mode of inheritance is not virtual, then we cant instantiate any of the class in the hierarchy, as all will be Abstract classes.


Multiple Inheritance

Hi, this is a good article to understand about the virtual inheritance. I came across a strange result, which I could not understand.

In the following program,

class BaseA ;

class BaseB ;

class BaseC ;

class Derived : public virtual BaseA, public virtual BaseB, public virtual BaseC

;

int main() Derived d; int size = sizeof(d); return 0;

the size value is 12. But as specified according to the above layout there should be only one virtual base pointer, so size value should be 4. I tried removing all virtual keywords in the above code and value of size was 2. What could be the reason for this behaviour. I am using Microsoft VC++ compiler. BaseA, BaseB, BaseC, Derived have empty definitions. The flower brackets are not appearing.

Multiple Inheritance

Hi,

You are getting this behavior because you are using Microsoft C++ compiler. In MSC++, they keep one pointer for each virtual base class. So, 4 for each BaseA, BaseB, BaseC. So size of Derived will be 12.

I am trying to figureout why its showing size as 2 when you inherit non-virtually, (by removing virtual keyword)! I have tried the same and its same result. When I keep only BaseA, BaseB, size will be 1 byte!! Its something which I could not understand. Will try to find out and let you know.

Please check the same code on g++ or c++ compiler (on UNIX system). Size will be just 4 bytes for virtual inheritance with 3 base classes and size will be 1 byte for non-virtual inheritance with 3 base classes. The reason is, in c++ or g++ compiler, they store all the virtual base pointer in one location and keep that location itself.

Regards Girish