Skip to main content

Multiple Inheritance

In C++,  a class can inherit from more than one class. This is called multiple inheritance.

Some other OOPS languages like Java, do not support multiple inheritance.


Syntax for multiple inheritance is

class cls-name:public base-cls1,public base-cls2,public base-cls3
{
/*code*/
}

We are specifying multiple classes as base classes here. The base class list is comma separated and has access specifier for each base class. Each of these base classes must have been declared earlier.

Derived class objects contain sub-objects of each of these base classes.
class LandAnimal
{
    int legs;
 public:
 void walk()
 {
 cout<<"walks";
 }
};
class WaterAnimal
{
   int fins;
 public:
 void swim()
 {
 cout<<"swims";
 }
};
class Amphibian:public LandAnimal,public WaterAnimal
{
};
int main()
{
 Amphibian frog;
 frog.walk();
 frog.swim();
} 

Output
walks
swims

In the above example, class Amphibian inherits from 2 classes - LandAnimal and WaterAnimal.

The derived class has members of both these base classes. It can call both walk() and swim() functions. We construct an object of this class and it has both data members legs and fins and it has functions swim and walk.

 Size of a Ambphibian object = size of LandAnimal+size of WaterAnimal. 

 Constructors 

The derived class constructors and destructors in multiple inheritance will call all of the base class constructors and destructors.  The order of constructor calls is same as declaration order of base classes.

Amphibian constructor in the earlier example, will call LandAnimal constructor first, then WaterAnimal constructor and finally Amphibian constructor. And order of destructor calls is reverse of order of constructor calls. 

class A
{
 int n1; int n2;
public: 
 A(int a,int b):n1(a),n2(b)
 {cout<<"A ctor ";}
};
class B
{
 int n3; int n4;
public: 
 B(int a,int b):n3(a),n4(b)
 {cout<<"B ctor ";}
};
class MI:public B, public A
{
 int m; 
public:
 MI(int num=0):m(num),A(num,num),B(num,num)
 {cout<<"Derived ctor";}
};
int main(int argc, char **argv)
{
 MI obj1(10); 
}

output of program
B ctor A ctor Derived ctor

Here MI class declaration uses B followed by A in base class specification. So B ctor is called before A ctor when MI object is created even though in member initialization list, A class ctor is called first.

Dreaded Diamond problem

When there are multilevel and multiple inheritances together, derived class may contain more than one instance of an ancestor class. In such a situation, using a member from this ancestor class throws an ambiguity error.

class A
{
 int m;
public:
 void setm(int a){m = a;};
 int getm(){return m;}
 void print(){cout<<"m"<<m<<endl;}
};
class B: public A
{
public:
 B(){setm(10);};
};
class C:public A
{
public:
 C() {setm(20);};
};
class D:public B,public C
{
 /*multiple inherited class*/
};
int main()
{
 D obj;
 cout<<obj.getm();/*error*/
 obj.print();/*error*/
}
When we compile this program obj.print() and obj.getm() throw ambiguity error.

obj has members of A class duplicated one via B class and another via C class.

When calling print() or getm() , compiler is unable to decide whether to call these functions of B class or  of C class.

This is called dreaded diamond problem because  shape of inheritance diagram in this case looks like a diamond.

To avoid this error, virtual inheritance is used. 

Virtual Inheritance


In virtual inheritance, when there are multiple paths to a base class, this base class object is included only once. 

Virtual inheritance is implemented using virtual keyword before base class name in derived class definition.
class A
{
 int m;
public:
 int getm(){return m;}
 void setm(int a){m = a;}
 void print(){cout<<"A print";}
};
class B:virtual public A
{
/***code**/
};
class C:public virtual A
{
/***code**/
};
class D:public B,public C
{
/**this has only one sub object of A*/
};
int main()
{
 D obj;
 obj.print();/*No error*/
}

Both B and C classes use virtual inheritance.  By using virtual keyword, we are ensuring that  only one copy of a A class's member variables are inherited by grandchild derived classes.

So D which inherits from both B and C, it is inheriting only one sub-object of A class. So there is only one data member called m in D class and only one print() function. There will not be any ambiguity when any member of A is being used from D class object because D object will have only one sub-object of A class.

Also you can observe that obj  causes  A class constructor to be called only once and A destructor to be called only once.

Comments

Popular posts from this blog

Polymorphism

You hear the term Polymorphism too frequently with object oriented languages. Along with Inheritance and Encapsulation, polymorphism is one of the corner stones of object oriented design. What is Polymorphism, exactly? Polymorphism is a mechanism by which you provide single interface for multiple methods. (poly - many, morph - form). In C++, polymorphism can be compile time or run-time. Compile time polymorphism is provided with the help of overloaded operators/functions and templates. Run time polymorphism is provided with the help of virtual functions and late binding. Late Binding: Connecting a function call to function body is called binding. Most functions use early binding where this binding happens before the program is run - during compile time. This is also called static binding. Late binding (also called dynamic binding)  is when a function call is connected to function body at run time. This is done after looking at the type of the object. Late binding is ach

Operator Overloading

What is operator overloading ? Operator overloading is the process of customizing C++ operators for operands of user defined types.   When you have two objects of a class- num1 and num2 , you can write a function to add them such as  ans = add(num1,num2); That does not look neither simple nor intuitive. You would prefer to write      ans = num1+num2; as you would write expressions for basic data types like integers, floats etc.    This can be done using Operator overloading. Operator overloading lets you write such statements. That is, it lets you call your functions on objects using  +, - ,* etc.    + operator will call addition function on the object (when you write op. overloading function for +). * will call multiply on objects etc. Names of overloaded operator functions start with keyword operator followed by  symbol of the operator. e.g. +, - etc. Unary operator functions take 0 parameters for members. The operand for these function is the object cal

Constant members of a Class

A constant  is a value which can not be modified. As in C, we can have literal constants using #define and we can have enums and we can define a variable to be const .  Let us look at const variables here. By declaring a variable as const , we ensure that it is not modified accidentally. Any modification to a constant will give a compilation error. A const should always be initialized while defining. In the program below,  assignment to pi gives a compiler error because pi is defined as const and code is trying to modify this. int main () { const float pi = 22.0/7 ; int radius = 12 ; radius ++ ; /*ok*/ pi = 3.14 ; /*error*/ }   Constant parameters to functions Even function parameters can be const ant. We have earlier discussed that making a reference parameter as constant will avoid the function from accidentally modifying the argument. void printnum ( int & n) { cout << n ++ ; } void printnum2 ( const int & n) { cout <&l