Inheritance

Suppose we have a class such as:

     class IntList
     {
      private:
       int * A;                  //Points to the array's 1st element
       int Size;                 //Size of the array
      public:
       IntList();                //basic constructor
       ~IntList();               //destructor
       void Insert(const int &); //add a value at the end of the list
       void Print() const;       //Print the list.
       ... various methods ...
     };

We decide that it would be nice to have a class called SortIntList which is just like IntList except that it is sorted. This would involve doing a lot of copying and making minor changes, as well as a bit of new work.

It would be nice to have a way to avoid all of this repetitive work. C++ allows us to do this with a mechanism called inheritance. That is to say, we can derive the SortIntList class from the IntList class. It would look something like this:

     class SortIntList : public IntList
     {
      private:
       bool Direction;  //true for Ascending, false for Descending
     };

For this to make sense, we will need, at the top of the file "sortintlist.h":

     #include "intlist.h"

We refer to IntList as the base class and we refer to SortIntList as the derived class. The derived class inherits the members of the base class.

Suppose we declare some variables:

     IntList X;
     SortIntList Y;
     IntList * P;
     SortIntList * Q;

As X is an IntList, it has data members called A and Size. As Y is a SortIntList, it has data members called A, Size and Direction. Notice X does not have a member called Direction.

What about the pointers? A pointer is limited by its declaration:

     P = &X; //A friend could use P to access X.A or X.Size.
     Q = &Q;  //A friend could use Q to access Y.A, Y.Size or Y.Direction
     P = &Y;  //Although Y has a member Y.Direction, P cannot be used
              //to access it, as P thinks Q is an IntList.
     Q = &X;  //This is illegal.

There is a way around this problem. See the section below on "virtual methods".

We may want to initialize Direction in the constructor for SortIntList. We can do this by extending the constructor for IntList:

     SortIntList::SortIntList()
     {
      //The constructor for IntList will be called automatically.
      Direction = true;
     }

The derived class can have more methods than the base class as well as more data members. It can also override method of the base class. For instance, the SortIntList class will probably have a method (listed in its definition)

     void Insert(const int &); //add a value where it belongs

which will add a value to the list but maintain the order. When we use this method, as in:

     SortIntList Y;
     Y.Insert(17);

the method called will be the version of Insert() that belongs to to SortIntList rather than the version that belongs to IntList.

If we want to use the base class's method inside a method of the derived class, we can refer to it like this:

     IntList::Insert(17);

To override a method in the base class, a method in the derived class must have the same name and the same argument list.

Likewise, in SortIntlist, we may want to print a bit more information. We may have a method in SortIntList:

      void Print() const;       //Print the list.

and its code can make use of the IntList method:

     void SortIntList::Print() const
     {
      IntList::Print();
      if (Direction)
       cout << "Ascending order" << endl;
      else
       cout << "Descending order" << endl;
     }


Three kinds of access for members

Up to this point, we have always had two levels of access for members of a class: public and private. Once we are dealing with derived classes, there is a third option: protected.

That is, members of a class can be listed with 3 kinds of access:


Three kinds of access for derived classes

In the above example, we have

     class SortIntList : public IntList

There other choices besides "public" here. We could instead have

     class SortIntList : protected IntList

or

     class SortIntList : private IntList

What do these mean? Suppose B is a base class and D is the derived class.

How do the two access specifiers interact?

     Base Class Member        Derived Class             Result as a Member
     is declared as:          is declared as:           in Derived Class:

       public                   public                      public
           
                                protected                   protected

                                private                     private

       protected                public                      protected

                                protected                   protected

                                private                     private
   
       private                  public                      inaccessible 

                                protected                   inaccessible

                                private                     inaccessible

That is, private members of A are inaccessible in B. Otherwise, The result is the more restrictive of the two access specifiers.

What about friends? A friend of a class has the same access as the members of the class itself, but not better.

Example

     class Person
     {
      public:
       string Name;
       string Address;
       long int Telephone;
       void print();
      private:
       ...(more stuff)...
     };

     class Student : public Person
     {
      public:
       string Major;
       void Print();
       ...(etc)...
      private:
       float GPA;
       ...(and so on)...
     };

The function Person::Print may produce output such as:

     John Q. Public 
     123 Elm Street
     8157583456

and we want the function Student::Print to do the same followed by a line printing the Major. The code for Student::Print might be:

     void Student::Print()
     {
      Person::Print();
      cout << Major << endl;
     }

Here we have to use the full name Person::Print inside the function to use the base class's method and to avoid unwanted recursion.

Both Print functions still exist, and the compiler can usually tell which one to use by knowing who is invoking it. That is, if we have

     Person H;
     Student K;

then H.Print will use Person::Print and K.Print will use Student::Print. For the case of K.Print, the definition of Print in class Student dominates the definition of Print in class Person.


More possibilities

If we have a number of classes, some derived from others, it may be useful to make a diagram of what is derived from what. We could have a class Director derived from a class Manager, which itself is derived from a class Employee.

Example:

     class Region                     //Region refers to a rectangular
                                      //subset of the screen:
     {                                // some rows and some columns.
      ...
     };
 
     class Box : public Region        //Box also draws a border around the
     {                                //rectangular region using a character
      ...                             //such as * stored in a data member.
     };

     class TextRegion : public Region //TextBox also writes a string in the
     {                                //middle of the box.
      ...
     };

Now suppose I would like to have a Region which has a border (so it is a Box) and contains a message (so it is a TextRegion). I want to call it a "TextBox". I could make a class derived from Box or from TextRegion, but I want it to have the characteristics of both.

What I need is multiple inheritance. I can write:

     class TextBox : public Box, public TextRegion
     {
      ...
     };

and, as you might suppose, the subject becomes very elaborate.

Windows programming has an elaborate hierarchy of classes, many of them derived from a basic Window class.


Virtual methods

As mentioned above, we can easily have a situation in which a base class and a derived class each have a method of the same name. Under normal circumstances, the compiler will decided which of these is being called. Thus, if we have

     Person H;
     Student K;

then the compiler can make sense out of

     H.Print();
     K.Print();

This is known as static binding. There are situations in which we might not want this decision to be made until later. For instance, we might have something like this:

     void CallPrint(Person & P)
     {
      P.Print();
     }

Here the argument is a Person, but it would be legal to say

     K.Print();

(because a Student is a Person) and we may prefer to have the Student version of Print used.

The way we can fix this is to make the Person version of Print a virtual function by adding the word virtual to its prototype:

     virtual void Print();

The term "virtual" tells the compiler that we expect to override this method with a method in a dervived class, and also that we want the choice between the two to be made at execution time. This is known as dynamic binding.

It is not required to mark the Student class's version of Print as virtual as well, but we often will do so anyway as a reminder.

Notice that the argument P of CallPrint is a reference. This technique will work if P is a reference, and it would work if the argument is a pointer, but it will not work for a value argument, as we would then be working with a copy of the actual argument, and the copy would be made by the Person copy constructor, so the result would certainly be a Person.

In some situations, we may have a number of different classes in mind, all derived from some common ancestor, and we do not intend to make any direct use of that common ancestor class. In that case, those methods of the ancestor class could be pure virtual functions. The prototype for a pure virtual function looks like this:

     virtual void Print() = 0;

and there is no code for the function at all. The "= 0" on the end is what indicates this is a "pure" virtual function. It exists only so we can have other functions overriding it.

If a class contains one or more pure virtual functions, it is called an abstract class. It is quite possible to have a class in which all methods are pure virtual functions.

How does dynamic binding work? That varies from one compiler to the next, but one version goes something like this: If a base class has any virtual methods at al, then the compiler creates a table (call it the V-table) containing the addresses of those methods. It does the same for each class derived from the base class. Each instance of the base class (and therefore of classes derived from it) will contain an extra data member (hidden), which is a pointer to the V-table for that class. It is initialized by the constructor. The compiler recognizes each call to a method listed in the table and generates code that will follow the pointer to the V-table, look up the address of the appropriate method and then go execute it. (If that sounds complicated to you, you are far from alone.)