Skip to main content

Inherticance in C++

What is inheritance?

In object oriented programming, Inheritance is a mechanism by which a new class can be created as an extension of an existing class. Inheritance helps us in code reuse.

The existing class is called base class /super class  and new class is called derived class / sub class  or inherited class.


Derived class is an extension of base class. It inherits all the members of the base class (even the private members). And it can have new members of its own.

Base class members which are public and protected are accessible from derived class object. Private members of base class are not directly accessible in derived class but can be accessed using base class methods.

Syntax

class derived-class:access-specifier base-class
{
/* members*/
};

We write derived class name followed by :, followed by public/private/protected followed by base class name. Then the usual definition of class is written where in we write new members of the class.

The base class must have been declared earlier. Access-specifier tells the mode of inheriting. It can be one of public, private or protected.

public is most commonly used access specifier. If omitted, access defaults to private. The table below shows how the access specifier controls the access of base class members.
Public Inheritance Public members remain public
Protected members remain protected  in derived class
Protected Inheritance Public and protected members become protected in derived class
Private Inheritance Public and protected members become private members in derived class

Public access specifier is the most commonly used mode for inheritance. If public access specifier is used, all public base class members are accessible in derived class and they remain public in derived class too. Which means they can be accessed from outside of derived class too.  And protected members of the base class remain protected in derived class, which means they are not accessible outside the derived class except for derived classes of derived class.

Protected access specifier makes the both public and protected members as protected members in derived class.

Let us look at an example of inheritance.


class A
{
 int n;
public:
 void setn(int m)
 { n = m; }
 int getn()
 { return n;}
protected:
 int n0;
};
class B:public A
{ 
 int n_der;
public:
 void print();
};
void B::print()
{
 cout<<getn()<<n_der;
 //cout<<n;//invalid
 cout<<n0;//valid
}
int main()
{
 B obj;
 obj.setn(10);
 cout<<obj.getn();/*obj can call base class function*/
 obj.print();
}

Here B is a derived class of A. That is to say, B is specialized class of A. B has all the features of A plus extra. So B has all data members of A and all methods of A.

 obj  is an object of derived class B, has the following data members - n and n_der and n0. n and n0 are inherited from base class A and n_der is a new member added in B.

obj has 3 member functions - getn(), setn() and print().

In the print() function of B class, derived class can access n0 because it is a protected member in base class. But it can not access n because n is private in base class.

Note that private member n can  be accessed by B class using getn() and setn() functions.

Which members of base class are inherited by derived class? 

Derived class inherits all the members of base class except for
  1. Constructors and destructors
  2. Overloaded operators
  3. Friend functions
Each object of a derived class IS AN object of base class.   

Protected members of a class

Let us revisit the access specifier "protected" in a class. A class member which is protected is not accessible outside of a class. But it is accessible from methods of derived class.


class A
{
   int n;
protected:
   int m;
};
class B:public A
{
   public:
     void print();
};
void B::print()
{
     cout<<n;//error
     cout<<m;//No error
}
In class A above, m is a protected member. It can not be accessed from outside. It can't be accessed from main() function. But it can be accessed from B class. print() function of B class, trying to display m does not produce any error. But display of n produces a syntax error.

So a protected member is like private member, but it can be accessed from derived class objects.

ISA

Remember that derived class object is a base class object.

A function which has a base class parameter can be sent any base class object as argument or any derived class object as argument.

So whenever a base class object is needed, you can use a derived class object in its place. But the reverse is not true. A base class object is not a derived class object.


class Dog:public Animal
{/*code*/};
void printAge(Animal a)
{
 cout<<"Age of animal is "<<a.getAge();
}
int main()
{
 Dog d1;
 Animal a1;
 a1 = d1;/*valid*/
 // d1 = a1;/*invalid*/
 printAge(a1);
 printAge(d1);
}

Here d1 is an Animal class object too. But a1 is not a Dog.
 
Now the function printAge() can be called with an object of animal class. But the argument to printAge() function can also be an object of derived class of Animal. But it can also be called with Dog object. Because Dog is a derived class and hence Dog is also an animal. 

Base Class
|
|
|
Derived Class

But when an object is upcasted (converted from derived to base class. Called so because of inheritance diagram), it will contain only base class elements and will lose derived class information. This is called object slicing.
 
If in the above example Dog class had extra data member- say Name, this member will not be present in the printAge function.

Object slicing can be avoided by using references or pointers.

Constructors and Destructors of Derived Class

When an object of derived class is created, first, constructor of base class is called implicitly, followed by constructor of derived class. If base class does not have default constructor, a compiler error is generated.


When derived object is being destroyed, first destructor of derived class is called  and then destructor of base class is called.

class A
{
public:
 A(){cout<<"A ctor ";}
 ~A(){cout<<"A dtor ";}
};
class B:public A
{
public:
 B(){cout<<"B ctor ";}
 ~B(){cout<<"B dtor ";}
};
int main()
{
 B obj; 
 cout<<"Hello world ";
}

Output of the above program will be
A ctor B ctor Hello World B dtor A dtor

When obj is created, it first calls base class constructor (A constructor), then it calls derived class constructor (B).

When main function is terminated, object obj is destroyed. During destruction, the program first calls derived class destructor (B) and then calls base class destructor. 

Member Initializer list for Base class constructor

If base class has no default constructor, then derived class can not call base constructor implicitly. This generates a syntax error.

class A
{
public:
 A(int m){/***code***/
};
class B:public A
{
 /*code*/
}
int main()
{
 B obj1;
}

The program above fails to compile because our base class A has no default constructor. B constructor for obj1  tries to call A constructor but fails.

In such situations, derived class must define a constructor, which must call base class constructor explicitly  in its member initializer list.
 

class B:public A
{
public:
 B():A(0){/*B ctor*/}
/*code*/
};

B constructor here calls non-default A constructor as A(0)


As we can see in the above code, we are calling parameterized constructor of base class using A(0) - which calls A constructor with argument 0.  And this must be called in member initialization list of derived class constructor. It can not be in the body of B constructor. 

Name Hiding

Derived class object can call all the public and protected methods of base class. But what if base and derived class methods have same name?

If derived class redefines a base class method with same name but different types or number of parameters, then base class version of function can not be called in the derived class. It gets hidden.

class A
{
public:
 void print();
};
class B:public A
{
public:
 void print(int n);/*this hides 
print function of base class*/
};
/* functions here*/
int main()
{
 A objA;
 B objB;
 objA.print();/*correct*/
 objB.print(10);/*OK*/
// objB.print();/*invalid */
}

In the above example, objB.print() is invalid because print() function is redefined with different signature in derived class. So print() with no parameters can not be called using B class object. Remember print() can be still be called using objects of A class.

If it is necessary to call such a hidden function from derived class,  it can  be called using scope resolution operator and base class name
 as
  objB.A::print()

Function Overriding:


A derived class can provide its own version of base class function. This overriding function in derived class must have exact signature as base class function. 

class A
{
public:
 void print()
 {
 cout<<"Hello world";
 }
};
class B:public A
{
public:
 void print()/*overriding function*/
 {
 cout<<"Goodbye world";
 }
};
int main()
{
 A obj1;
 obj1.print();/*prints hello world*/
 B obj2;
 obj2.print();/*prints Goodbye world*/
}


Now if print() is called from object of B class, it invokes the overriding function of derived class.

Function overriding and  virtual functions is  used for implementing polymorphism in C++. 



You can get these notes and also programs, quiz on C++ by downloading the app Simplified C++ by Hegdeapps 

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