Inheritance - the Basics

Hierarchical Relationships:

Many types of classes that we create can have similarities.  In fact, many objects are very closely related, some to the point that they share certain information and behavior.  When this is the case, it is useful to take advantage of the object-oriented programming technique known as Inheritance.  To do this, we factor out the common features into a single class, usually referred to as a base class.  Then, we can build derived classes that will share all of the data and functionality found in the base class.  The derived class will inherit all of the base class features.

This base class / derived class relationship is often referred to as an "is a" relationship, because the derived object really is an instance of the base object -- it's usually just a bit more specific (like a subcategory).  Examples:

A class called Geometric_Object.  From this base class we could derive classes like Circle, Square, and Line.
A class called Sport.  From this class we could derive classes like Football, Baseball, and Soccer, because each one is a Sport.
A class called BankAccount.  From this we could derive the classes Savings, Checking, Debit (each is a type of account).
A class called Vehicle.  From this we could derive classes Car, Train, Bus (each one is a vehicle).  Furthermore, we could use the Car class as a base class from which to derive more new classes, such as Ford, Toyota, and Honda.

Note that this is different from the "has-a" relationship, in which objects are embedded inside of others (i.e. one object is a component of another).
 

Declaring Derived Classes:

To declare a class as a derived class, just add a little bit of syntax to the class declaration block.  Here is the format:
 class derivedClassName : public baseClassName

The word "public" in this format causes derivedClassName to be publicly derived from baseClassName.  The base class would be declared normally somewhere above (or in an included file).  This essentially means that the protection levels in the derived class are the same as in the base class.  (Note:  It is possible to derive with different protection levels -- we will not worry about those here).

So, the class declarations for some of the above examples might look like this:

 class Sport 
 { 
 .... 
 }; 

 class Football : public Sport 
 { 
 .... 
 }; 

 class Checking : public Account 

 class Baseball : public Sport 

 class Car : public Vehicle 

 class Honda : public Car 

Notice that in the last two declarations, Car inherits everything in the Vehicle class, and Honda inherits everything in the Car class.  Because of this, Honda has also inherited everything from the Vehicle class as well.

Example:  classes in a drawing program

Protection levels:

Remember that the protection levels we have are public and private.

public - Any member that is public can be accessed by name from anywhere.
private - Any member that is private can be accessed directly only by the class in which it is declared.

This poses a problem, since we would like derived classes to have access to the members that it inherited from the base class, but we still want protection from outside access.  For this reason, there is a third level of protection for member data and functions, called protected.

protected - Any member that is protected can be accessed directly by the class in which it is declared, and by any classes that are derived from that class (but not from anywhere else).

Protection levels in the drawing program example

Constructors in derived classes:

We know that when an object is created, its constructor runs.  However, what happens when a derived object is created?

In this case, the derived object "is-an" instance of the base class as well.  So, when a derived object is created, the constructors from the base and derived classes will run.  The order in which they run is important, too -- the base class constructor runs first, then the derived.

Using the examples given above, consider these object declarations:

 Vehicle obj1; 
 Car obj2; 
 Honda obj3; 

In the first declaration, only the Vehicle() constructor runs.
In the second declaration, the Vehicle() constructor runs for obj2, followed by the Car() constructor.
In the third declaration, the constructors that run, in order, are: Vehicle(), Car(), Honda()

Note:  When an object goes out of scope, the destructors will run in reverse order:   ~Honda(), ~Car(), ~Vehicle()

You can verify for yourself in code what order the constructors and destructors run in.  Click here for an example.

Constructors with parameters:

Since no parameters were specified in the above examples, the default constructors (i.e. no parameters) are used.
What if we have parameters?   Remember in the Fraction class, we could declare the following:

  Fraction f(3,4);        // calls constructor with parameters 

So, with a derived class, we might want to declare:

  Honda h(2,3,4,"green","Accord");    // wants to send in 5 parameters 

However, the 2 and 3 might be codes intended for a variables like "vehicleCategory" and "idNumber" in the Vehicle class;  the 4 and "green" might be intended for variables like "numDoors" and "carColor" in the Car class; and the "Accord" might be intended for a variable "model" in the Honda class.  This means that the first two parameters are needed by the Vehicle constructor, the second two are needed by the Car constructor, and the last is needed in the Honda constructor.  How do we distribute the parameters to the appropriate places?

To do this with derived classes, use an Initialization List.  And initialization list is something that can go along with any function definition.  The format is:

 function prototype : initialization list 
 { 
     function body 
 } 

We will use the initialization list to call the next higher constructor explicitly, in order to send the needed parameters up to the parent constructor.  An initialization list is run before the function body actually runs.   Here is how the constructors might look in our car example:

Vehicle::Vehicle(int c, int id) 
// this parent constructor uses the first two parameters 
{ 
    vehicleCategory = c; 
    idNumber = id; 
} 

Car::Car(int c, int id, int nd, char* cc) : Vehicle(c, id) 
// this function passes the first two parameters up to Vehicle, and uses nd and cc 
{ 
    numDoors = nd; 
    strcpy(carColor,cc); 
} 

Honda::Honda(int c, int id, int nd, char* cc, char* mod) : Car(c, id, nd, cc) 
// This function passes the first four parameters up to car, and uses mod 
{ 
    strcpy(model, mod); 
} 

Drawing program example, with constructors
 

Function Overriding:

Suppose we have the following base class:

 class Student 
 { 
   public: 
     void GradeReport(); 

   ...  (other member functions and data) 
 }; 

Let's assume that the "GradeReport" function will print out a grade report for a Student.

Now, take the following as classes derived from Student:

 class Grad : public Student 
 class Undergrad : public Student 

Since these classes are derived from Student, they inherit everything from the Student class.  So, we could build the following objects and make the following function calls:

 Student s; 
 Grad g; 
 Undergrad u; 

 s.GradeReport(); 
 g.GradeReport(); 
 u.GradeReport(); 

The Grad and Undergrad classes both inherited the GradeReport function, so they can call it.  However, what if grade reports look different for undergrads and grads?  Then, these classes need to have their own functions!  With inheritance, a derived class can create its own version of a function in the base class, with the exact same prototype, which will override the base class version:

 class Grad : public Student 
 { 
   public: 
      void GradeReport(); 

   ... (other stuff) 
 }; 

 class Undergrad : public Student 
 { 
   public: 
      void GradeReport(); 

   ... (other stuff) 
 }; 

So, in the calls listed above, each object calls it's own version of the GradeReport function:

 s.GradeReport();    // runs Student's version 
 g.GradeReport();    // runs Grad's version 
 u.GradeReport();    // runs Undergrad's version 

Now, does this mean that for the grad object g, the parent version is no longer accessible?  No!  Remember, a derived class inherits everything from its parent class.  If, inside the Grad's version of GradeReport, you would like to call upon the parent's version as well (usually done if you want to split the work and let the parent function do as much as possible, based on data stored in the base class), you can.  Here's an example -- these are outlines of  the function definitions:

 void Student::GradeReport() 
 { 
    ... processing done by parent function ... 
 } 

 void Grad::GradeReport() 
 { 
    Student::GradeReport();    // explicit call to parent function 
    // other processing specific to Grad's version 
 } 

Notice that you can explicitly call the parent's version of the function by specifying the parent class name and the scope resolution operator:

    className::memberName 

Examples of function overriding for the drawing program example.