19 Varieties of Inheritance

Bhushan Trivedi

epgp books

Introduction

 

In this module, we will see what exactly an inheritance is and how it is provided by the C++ language. The C++ language provides inheritance in more than one way and each of them is suitable for a typical case. We will see what base and derived classes are, how a derived class can be derived further and what the consequences are. We will see how the access specifiers of thein the base class changes as per the specification provided in the inheritance definition.

 

A base and a derived class

 

Inheritance is about generating a new class form an already existing class, inheriting all its data and function members but not limited to them and have an ability to add more or even override existing members. It is one of the most useful properties of C++ programming. A better-defined class can be inherited further by many other classes. For example, a vehicle class is defined and then further inherited by classes like Car, Scooter, Bike, Boat etc. Once a Vehicle class is defined with data members like vehicle registration number, purchase date, etc. information, all other classes need only to inherit them and do not need to redefine them in their respective classes. The vehicle class is called a base class and the classes like Car are called inherited classes or derived classes.

 

In short, the base class is a class from where the other class is derived, that means it is the class which is a parent class for some other class. The derived class is a class which is inherited from some other class. This means it is a child class of some other class

 

Isa property and need for disciplined programming

 

A general conception is that whenever one needs to define a class (let us call it class D) and there is some class with similar set of attributes (let us call it class B), the best way to define the class D is to inherit that class from that class B and remaining items are to be defined in class D. Not really so.

 

It is true that using inheritance helps us to start from a platform but that is not advised to be used every now and then. Ideally, only when the class to be inherited has the isa property with the class from where it is inherited from, it is good to inherit and not otherwise.

 

An isa property between an object of class D and an object of class B is true only when an object of class D is considered to be an object of class B as well. For example, an element of class scooter (a scooter GJ-o1-RAS 2345, for example) is an element of the class vehicle, an element of the class car(for example a car MH-01-ABC-9876) is also an object of class vehicle etc. is true. That means the class scooter has an isa relationship with the class vehicle and the class car has an isa relationship with the class vehicle. In that case, the scooter can be inherited from the vehicle and car can also be inherited from the vehicle.

 

We will not stress this issue further. It is important for us to know that the isa relationship is an important criterion to decide whether one should inherit from a class or not. The programmers should refrain from inheriting when there is no real isa relationship.

 

In short, the ISA property can be defined as follows. When all objects of class A is also objects of class B, class A is said to have ISA property with class B.

 

Having a single platform for multiple classes

 

Once a platform is ready with a base class, many derived classes can be generated from it. We have already seen the example of the vehicle class. Interestingly, one can also use inheritance to extend the functionality of a class. For example, we may have a class called MCAStudent from where we can generate an extended class called GTUMCAStudent (MCA students who are enrolled in GTU). Similarly, we might have a class called Emp (employee) which we might inherit into Teacher, Clerk and Peon classes. Such hierarchy is not only useful to model real-world cases but programming cases as well. Working with Windows MFC (Microsoft Foundation Classes) one may find a class called CDialog which represents a dialog box. One can extend that class into one’s own dialog box with our additional functionality.

 

Deriving and manipulating common attributes at a single place

 

Designers, many times, find common elements across multiple classes. For example, one might come across common elements while dealing with classes batsman, bowler, and fielder. He may come across such cases where there are registers maintained by the manual system to store information about cricket matches and players. We get information about batsman, bower, all-rounders, fielders, and wicket-keepers. After examining, we may find that the registry entries of all these entities contain many redundant fields. Instead of storing those redundant entries in three different classes, it is always a good idea to store them in a single class and inherit all three classes form this new class. In this case, naming this new class is also simple, we can name it as a CricketPlayer class and let it have all information which is common across a batsman, a bowler, and a fielder class. For example, name, either right handed or left handed, height, weight, yo-yo test result, age, no of ODIs played, no of Tests played, No. of t20s played etc. can be kept inside that class. When we inherit the batsman class from this CricketPlayer class, it automatically has all this information. The advantage of this mechanism is that the information which is common is kept in a single place. If there is an update, it is done in a single class. For example, consider a case where the cricket academy decides that the player information must have added a few fields. For example, the bank account number, branch name, IFSC code and mobile number because all payments now onwards to all cricket players will be through online banking only. In the second design, we only need to change in the CricketPlaye class and once it is inherited to other classes they will automatically have the updated structure. Instead, if we have chosen the first approach, we need to modify all three classes with redundant information. This approach is not only tedious but error prone also. Consider a case where the programmer has changed the content of two of the classes and forget to change the third class (fielder). Those unfortunate players who are registered as fielders won’t get their payments! Such inconsistencies are always nightmares for administrators and thus avoided at any cost. Thus the design with inheritance scores over the other approach hands down.

 

Advantages of inheritance

 

If one needs to get better and robust solutions quickly, inheritance is a way out. Suppose we need to design a website, with multiple web pages. One can design a default webpage, template, with the header of the page, border if need be, components like text boxes and labels, specific size and color etc. Whenever a new page is to be created, this default page is inherited. The user now needs to only change things that he wants different on his own page. It is quite possible that the default page has many functionalities that the user might not want, for example, no radio button is required, so he might remove them from the page. He might also add a few more things to the page on his own. The best thing about such a coding is that the web page programmer only needs to concentrate on the additional part and does not need to start from scratch. Another good thing about this approach is that the base class (in this case the default page), is more robust because when multiple users inherit that and use that in their programs if there are bugs in the class design, they have a higher probability of being found out and removed.

 

The unforced inheritance relationship by C++

 

There are many languages, most notably Java provides the inheritance as its important feature. The difference in C++ is that it is not forced on the programmer. In Java (and quite a few other OO languages, obviously for their own reasons), every class that one defines is inherited. For example, Java contains a root class called Object from which all other classes, including classes defined by the users, are inherited. The C++ inheritance is not forced on the programmer and a programmer is free to write a code without having inheritance. In earlier modules, whatever coding we have done, is done without any implicit or explicit inheritance process. In subsequent modules, this difference will become more and more clear. The idea in C++ is to give a programmer a freedom to choose. If he feels OO programming is right for him, he can do so if he feels not, he is not forced into it.

 

Let us look at the advantages of using inheritance.

In summary, there are two different approaches used for inheritance, top down and bottom up. They are defined as follows. Bottom-Up Design is a design where the inherited classes are designed before the base class, and then the common attributes of the inherited classes are combined together to form a base class. On the contrary, the Top Down Design is a design where the base classes are designed first and the specialization of the base classes are defined as the derived classes at a later stage.

 

Easier mapping of real-world hierarchy

 

The real world is full of objects and specializations of the same. The C++ design is so designed that it is very easy to map a real world hierarchical objects into the language code. There are many real-world hierarchies like vehicle-scooter etc. in the real world. When the language provides the inheritance in the design, modeling such a hierarchy becomes easier as the readymade syntax can be used to map such a hierarchy. This facility makes the program more readable and intuitive. For example, we will soon see that an often demonstrated hierarchy in C++ pedagogy is the shape class into ellipses and rectangles. The circle can be inherited from ellipse and squares from rectangles1. C++ provides a direct way to implement this hierarchy as is and that is why it is a great tool to model such hierarchy. C++ also provides an excellent support for manipulating such objects which we will throw more light on during our journey of inheritance in this as well two more subsequent modules.

 

Before we proceed further, there is an important point to discuss. The isa relationship that we have talked about earlier is not the same as the instance of relationship or part of the relationship. Entities in the world have many possible relationships between them. Isa is just one of them. An object has an instance of relationship which the class while attributes of the class have part of the relationship with the class. For example, Ganesh (being a student) has an instance of relationship with the class student. Name attribute has part of the relationship with the class student and so on. One must learn to understand the relationship and model them properly in the program. The C++ language (or any other language for that matter) only provides ability to model such relationship and implement them for manipulation, if the user has modeled a wrong relationship, (for example Maharashtra election is inherited from Gujrat election2), the language (or the compiler) does not object but will not be able to provide the solution which is flexible and extendable. Right design is a

 

1  The author of this module has made an interesting observation. Almost every book he read on introductory C++ contains this hierarchy of shape and rectangles and circles and so on. It seems one will not endorse a book being a C++ textbook if such a hierarchy is not present in it!

 

2      Inheriting Maharashtra election from Gujrat is actually done by a company who is implementing a government project, just to avoid additional coding. However, this design has a basic flow, Maharashtra election does not have an isa relationship with Gujrat election. If ever Gujrat elections change its functioning, Maharashtra election prerequisite to a good program. A language can only provide ways to implement a good program but cannot enforce right design. A programmer must learn to be disciplined and do not misuse the features provided by the language.

 

In short, Instance of relationship is the relationship of an object with a class is called an instance of relationship. It indicates that the said object is an instance of the class. The Part-of relationship is the relation between an attribute and a class is called part-of relationship. An attribute is considered to be a part of an object of the class and so is part of the class itself. Many other similar relationships do exist between objects.

 

Inheritance in C++

 

C++ implements inheritance in a very simple fashion with equally simple syntax. We will discuss that in this section. Let us recap what we have discussed so far. The data members of the base class are available to the derived class after inheritance without redefining them in the derived class. Interestingly, there are three different ways to inherit classes which decides how those base class members are available to the derived class members and objects. The base class members are associated with some access specifiers while being defined. We have studied two types of access specifiers so far, private and public. If a member is defined as private, it is available to members but not objects while public members are available to both. We will be looking at one more access specifier called protected in this module, which is as good as private unless the class is inherited.

 

Let us have Base defined as a base class. If we want to inherit the Derived class from it, we can do it simply by writing following.

 

class Derived: public Base

 

{

<body of the class Derived>

}

 

The syntax is straightforward, the class definition happens as normal class definition header (the keyword class in the beginning and name of the class following it) and then followed by a colon (:) and then an access specifier (in above case word public indicates a public access specifier for inheritance) and finally the name of the class from which we would like this class to inherit (Base in our case).

 

In other words, we can state that the inherited class is defined as

 

class <derived class name> : <access specifier> <name of the base class>

The access specifier value can be either from the public, private or protected. The derived and the base class names as what is preferred by the user and class as a keyword used in this definition.

 

How will be the use of different access specifiers change the behavior of the inheritance process? Let us try to see one after another. Let us begin with the most used inheritance type, the public inheritance.

 

Inheritance using public access specifier

 

Some other authors use the phrase visibility mode for what we have used access specifier.

However, we will continue using the word access specifier.

 

Consider Base as the base class already defined, we can define a new class Derived, inherited from Base using public access specifier as already seen in the previous section, but let us repeat the example of how a class is publicly derived.

 

class Base

{

  • // the data and function members of the base class

}

class Derived: public Base

Once we do this, the consequences are as follows.

  1. The public members of the base class are available to derived class members and objects as public members of the derived class itself. That means if Base class has a member b defined as public. A derived class object d can access b as d.b = <some value> and a derived class member f() can use b in its body.
  2. Private members of the base class are not available to the derived class directly. That means neither the data members can access them nor the objects. That means, if a derived class object is d and a base class private member is b, we can neither write d.b = <some value> nor a derived class function member f() can use that variable in its body. The compiler will flag an error if so. However, the derived class object can access a base class public member f() which can access the base class private member b, and thus the indirect access to the private variable of the base class is available to the derived class.
  3. If we have defined some members as protected, those members are considered protected in the derived class.

In short, the access specifiers of all the members do not change when the class is publicly inherited in the derived class.

 

Let us take an example to understand

 

//Program 20.1

/PbInheritance.cpp

class Base

{

int PrBaseInt;

public:

int PbBaseInt;

void SetPrBaseInt(int Value)

{

PrBaseInt = Value;

}

};

class DPb:public Base

{

int PrDInt;

public:

int PbDInt;

void SetPrDInt(int Value)

{

PrDInt = Value;

}

};

int main()

{

DPb oDPb;

 

//oDPb.PrBaseInt = 10;

 

//oDPb.PrDInt = 10;

oDPb.PbBaseInt = 10;

oDPb.PbDInt = 10;

oDPb.SetPrBaseInt(20);

oDPb.SetPrDInt(30);

}

 

The program demands some explanation. We have two classes Base and DPb (derived publicly). Base class have an int member PrBaseInt (Pr indicates private) which has an access specifier as private, so the base class objects cannot access PrBaseInt. However, a public member function of Base, SetPrBaseInt can access and set its value.

 

When the DPb is derived from the class Base, all these members with their original access specifiers are available to the derived class. That means the derived class object that we have defined, oDPb, it can access all base class public members. Look at the following statement.

 

oDPb.PbBaseInt = 10;

 

It accesses the base class int variable as if it is defined insider the DPb (which is actually not). Also look at following statement. It indicates another public member, this time a function member, SetPrBaseInt() being accessed.

oDPb.SetPrBaseInt(20);

 

However, there is one more indication of this statement. If you look at the definition of SetPrBaseInt, you will find that it accesses a private member of the base class, viz. PrBaseInt. That means, the derived class accesses a private member function via the public member of the base class. What if we write following code as a part of the derived class?

 

int DispPrBaseInt()

{

cout << PrBaseInt;

}

 

It will throw an error indicating that PrBaseInt is a private member of the base class and thus the derived class has no access to it. That means the private members of the base class are not accessed by members of the derived class. The earlier example showed that the derived class objects can still call the base class member function to set the value fo the PrBaseInt and thus can access that indirectly.

 

Now the final point. Why are two of the statements of the program shown below commented?

 

//oDPb.PrBaseInt = 10;

 

//oDPb.PrDInt = 10;

Obviously because the private variables of either base or derived class are not accessible to the derived class object.

In short, when a derived class is inherited using public access specifier, the inheritance is known as public inheritance.

 

Private inheritance

 

Now it is the time to see what happens if the class Derived is inherited using private access specifier as follows.

class Base

{

//  the data and function members of the base class

}

 

class Derived: private Base

You can see that now the access specifier is changed to private. Let us see the consequences of the private derivation.

 

1.      a public member of the Base is treated as private members of the Derived. That means a public member b of the base class cannot be accessed using a derived object d using the d.b notation. However, a public member f() of the derived class can still access b in its work.

2.      Private members of the Base are not available to the derived class exactly like the previous case. They are still available indirectly to the members of the derived class but not the object of the derived class.

3.      Protected members of the base class are available as private in the derived class. That means they are available to members of the derived class but not the objects of

 

the derived class.

 

Let us see an example to illustrate the point.

 

//Program 20.2

 

//PrivateInheritance.cpp

#include <iostream>

using namespace std;

class Base

 

{

int PrBaseInt;

public:

int PbBaseInt;

void SetPrBaseInt(int Value)

{

PrBaseInt = Value;

}

};

class PrDerived:private Base

{

int PrDerivedInt;

public:

int PbDerivedInt;

void SetPrDerivedInt(int Value)

{

PrDerivedInt = Value;

}

void SetPbPrBaseInt(int Value)

{

PbBaseInt = Value;

SetPrBaseInt(Value);

}

};

int main()

{

PrDerived oD;

 

//oD.PrBaseInt = 10;

//oD.PrDerivedInt = 10;

//oD.PbBaseInt = 10;

oD.PbDerivedInt = 10;

//oD.SetPrBaseInt(20);

oD.SetPrDerivedInt(30);

oD.SetPbPrBaseInt(10);

 

return 0;

}

 

Seems similar to the previous program? it is. We have the same base and derived classes with similar structure; i.e. same set of data and function members, apart from cleverly crafted base class public member functions to access private data members of the base class. The only other difference is that the inheritance is private now. However, this small change has a big consequence. The main has many statements commented now as they are not allowed to be executed. All base class members are now not allowed to be accessed by the derived object and only as a private member to the member functions. Look at following call. it sets the value of int defined inside Base as private. This a derived class public function which is accessible to the derived class object. It, in turn, calls a function which is a public function in the base which is available as private in the derived class so accessible to this object.

 

One more interesting observation is derived from this code.

 

void SetPbPrBaseInt(int Value)

 

{

PbBaseInt = Value;

SetPrBaseInt(Value);

}

 

The function SetPrBaseInt is called without an invoking object. How can it work? The function SetPbPrBaseInt is a member function. It gets the pointer to invoking the object, popularly known as the this pointer when invoked. The same this pointer is passed to this SetPrBaseInt function. Look at the call by oD object yet again.

 

oD.SetPbPrBaseInt(10);

 

The SetPbPrBaseInt function is passed with &oD, the this pointer pointing to the object oD. That very pointer is passed to the SetPrBaseInt function. In a way, it is like calling oD.SetPrBaseInt() indirectly. That is why we say that the private members of the base class are available to the derived class members indirectly after inheritance.

 

Now it is the time to introduce the protected access specifier. Protected access specifier makes sense with inherited classes only and that is the reason why we have introduced protected access specifier here.

 

In short, when a derived class is inherited using private access specifier, the inheritance is known as private inheritance.

 

 

Protected Access specifier

 

If a class is not inherited further, a protected member is as good as a private member. That means a protected member is accessible to public members of the class and friend functions but not to the objects. However, the difference is clear when the class with a protected member is inherited further into a derived class.

The difference between protected and private members of the base class is that the private members of the base class are not accessible to the derived class but the protected members are. They act as good as they are private members of the derived class. That

means public members of the derived class can access protected base class members after derivation.

 

Let us take an example to understand the difference. We will change private members of the base class into protected and see the changes.

 

//  Program 20.3 //Protected.cpp class Base

{

protected:

 

int PtBaseInt; public:

 

int PbBaseInt;

void SetPtBaseInt(int Value)

{

PtBaseInt = Value;

}

};

class Derived:public Base

{

 

int PrDerivedInt; public:

int PbDerivedInt;

void SetPrDerivedInt(int Value)

{

PrDerivedInt = Value;

}

void SetPbPtBaseInt(int Value)

{

 

PbBaseInt = Value; PtBaseInt = Value;

}

};

int main()

{

Derived oD;

 

//oD.PtBaseInt = 10;

 

//oD.PrDerivedInt = 10;

//oD.PbBaseInt = 10;

oD.PbDerivedInt = 10;

//oD.SetPrivateBaseInt(20);

oD.SetPrDerivedInt(30);

oD.SetPbPtBaseInt(10);

}

 

Observe the difference between the previous program and this program

 

protected:

int PtBaseInt;

 

Above statement defines an integer as protected in the base class. Following indicates that this PtBaseInt is not available to the object of the derived class.

//oD.PtBaseInt = 10;

 

However, the critical difference is highlighted in the following code.

void SetPbPtBaseInt(int Value)

{

PbBaseInt = Value;

PtBaseInt = Value; // the difference

}

This derived class function can access the protected member directly. It wasn’t the case with the private member in the previous program. We had following code in the previous program.

 

void SetPbPrBaseInt(int Value)

{

PbBaseInt = Value;

SetPrBaseInt(Value); // direct access to PrBaseInt wasn’t possible

}

 

Thus the biggest difference is the protected variable available for the function members of the derived class for manipulation which is not the case with the private members. Once we have learned how the protected data member is treated by the derived class, let us try to see what happens when the class is inherited in a protected fashion.

In short, when a derived class is inherited using protected access specifier, the inheritance is known as protected inheritance.

 

Protected Access specifier

 

When the class is not inherited further, the protected inheritance acts the same as private inheritance. Thus if the base class is inherited into a derived class as private or protected, there is no difference if the derived class is not inherited further. Protected inheritance acts the same as private. Here is how that is done.

 

class Base {     // Base class content }

 

class Derived:protected Base {              // Derived class content }

 

In short, protected access specifier is other than public and private specifiers. Unlike privately defined members, members which are defined as protected members are available to the derived class, otherwise, they have all the same characteristics of a private member.

 

Let us also recap what we mean by directly available. when a member (a function of a derived class) can access another member (a data member of a base class normally), the data member is said to be accessed directly. On the other hand, when a member (a derived class function normally) needs to call another member (a base class function normally) to

 

access a base class member, the said base class member is not accessible directly but indirectly.

 

The base class elements are treated under protected inheritance as follows.

 

1.      The public members of the Base are treated as protected in the Derived class

2.      Private members of base are not directly available in the derived class

3.      If we have protected members in the base class, they are available as protected in the derived class. That means they are available for the member functions to manipulate.

 

Following is the summary of the entire discussion we have about this inheritance process. The members of the base class are available to the derived class directly only if they are either protected or public and as shown in the table. Whenever an already derived class is inherited further, it is called further inheritance.

 

 

Table 20.1 The inheritance and availability of members to the derived class

 

Member type in a base class Type of derivation Member type in derived
class
Private Private Not available
Protected Not available
Public Not available
Protected Private Private
Protected Protected
Public Protected
Public Private Private
Protected Protected
Public Public

 

 

Observations

After looking at the entire inheritance process, here are some important observations.

 

1.      In case of public derivation, the effective specifier of the base class remains the same as in the derived class. The public remains public, protected remains protected and private remains private (but not available directly). If we further inherit DD from Derived, for example, both public and protected continue to remain the same.

2.      In case of protected derivation, both public and protected members of the base class are turned into protected and private members of the base class are not available directly.

3.      The members derived as private and protected are different when further inherited. Privately inherited members are not available for further derivation, and thus DD in

the previous case won’t have access to any members of the base class if the base class is inherited as private in the derived.

 

To illustrate the point related to further inheritance, let us have a table indicating what exactly happens when the class is derived further. Table 20.2 describes the point. You can see that instead of defining a member private, if the user defines it as protected, it is available to the derived class members and if inherited further, it is also available to further derived class members.

 

Public and private derivations also illustrate one more point. The public derivation is open and allows further derivation while private derivation just stops the further inheritance. Protected derivation allows derivation in the form where the members can access base class data but objects cannot.

Summary

 

In this module, we have seen how C++ provides inheritance. We have seen that ISA relationship is a must for a valid inheritance but the user can violate that without language picking it up. The public derivation is open and allows further inheritance while protected allows restricted access only to members. Private derivation just stops further inheritance.

you can view video on Varieties of Inheritance

References

 

  1. Programming with ANSI C++, Bhushan Trivedi, Oxford University Press
  2. www.stroustrup.com, homepage of Bjarne Stroustrup, the creator of C++