Inheritance

In software development we often find cases where there is a clear logical heirarchy to the components we are modeling.

For example, if we are implementing a game or a simulation we might have a variety of different types/classifications of vehicles:

In such a case, it would make sense to have the data and operations associated with the components also sit in a heirarchical framework.

For example, all vehicles might have some basic data and operations associated with them (current coordinates, facing, speed, weight, etc).

Then there might be additional properties/operations associated with all marine vehicles - which might be different than those associated with land vehicles etc.

Ideally we would like to define the properties and operations appropriate for each level or category of item, and to allow the more specialized versions to 'inherit' the properties and operations of the general classes they are a part of.

In the example above:

Syntax

Suppose we create a vehicle class whose data fields are just the vehicle's x,y,z coordinates and whose methods are a print routine to display its coordinates and an update routine to change its coordinates.
class vehicle {
   public:
      float x, y, z;  // coordinates

      // method to print the coordinates
      void print();

      // method to update the coordinates
      void update();
      
      // default constructor and destructor
      vehicle();
     ~vehicle();
};
If we now want to create a new class, AirVehicle, and have it inherit everything from vehicles we can do so with the syntax below.

Note that in the new class we can add any new fields and methods desired, and can replace any of the vehicle methods with new versions.

class AirVehicle: public vehicle {
   public:
      // track the vehicle's air and ground speed
      float groundSpeed, airSpeed;  

      // replace the vehicle print method with a specialized one
      void print();

      // add a method to update the speeds
      void updateSpeed();
};
If we create an AirVehicle object, it is possible to call either the AirVehicle print method, or its parent print method:
AirVehicle a;
a.print();  // calls the air vehicle print
a.vehicle::print(); // calls the parent version

Permissions

In addition to the public and private specifications for fields and methods, there is also a protected specification. If a field or method is classified as protected then it is accessible by methods in any derived classes as well as the methods in the original class.

It is also possible for the derived classes to specify how they will restrict access to the fields/methods they inherit. For instance, even though fields might have been public in the parent class, the derived class may wish to inherit them as protected or private.

The example below illustrates the different combinations of access permissions:

class parent {
   public:      // fields accessible to anyone
      int x;
   protected:   // fields accessible to parent and child methods
      int y;
   private:     // fields accessible only to parent methods
      int z;
};

class child1: public parent {
    // inherits x as public, y as protected,
    //    cannot access z
};

class child2: protected parent {
    // inherits x and y, treating them as protected,
    //    cannot access z
};

class child3: private parent {
    // inherits x and y, treating them as private,
    //    cannot access z
};

Constructor/destructor order

In C++ the constructors for parent classes are automatically run before the constructors for the derived class.

For example, if we have an AirVehicle class derived from a vehicle class and we create an object of type AirVehicle then the vehicle() constructor runs first, the AirVehicle() constructor second.

The reverse order applies to the destructors: e.g. ~AirVehicle() would run first, then ~vehicle().

Example

The example below shows a parent class and two different child classes derived from it, including cases where the children override the parent's print method and cases where they explicitly use the inherited method.

In the example's output you can follow the sequence of constructor/destructor calls.

#include <iostream>
using namespace std;

class parent {
   public:
      parent();
      ~parent();
      void print();
   private:
      char pdata;
};

class child1: public parent {
   public:
      child1();   
      ~child1();   
      void print();   
   private:
      int cdata;
};

class child2: public parent {
   public:
      child2();
      child2(int c);   
      ~child2();   
      void print();   
   private:
      int cdata;
};

int main()
{
   cout << "Child 1 default declaration" << endl;
   child1 C1;
   C1.print();
   cout << endl;

   cout << "Child 2 default declaration" << endl;
   child2 C2;
   C2.print();
   cout << endl;

   cout << "Child 2 override declaration" << endl;
   child2 C3(3);
   C3.print();
   cout << endl;

   cout << "Now the destructors begin" << endl << endl;
   return 0;
}
// parent implementations
parent::parent() {
   cout << "Parent constructor" << endl;
   pdata = 'P';
}

parent::~parent() {
   cout << "Parent destructor" << endl;
}

void parent::print() {
   cout << "Parent print " << pdata << endl;
}

// -------------------------------------
// child1 implementations
child1::child1() {
   cdata = 1;
   cout << "child1 constructor " << cdata << endl;
}

child1::~child1() {
   cout << "child1 destructor " << cdata << endl;
}

void child1::print() {
   cout << "child1 print " << cdata  << ":"; parent::print(); 
}

// -------------------------------------
// child2 implementations

// note the example here where the child constructor
//    explicitly identifies the parent constructor
child2::child2():parent() { 
   cdata = 2; 
   cout << "child2 constructor " << cdata << endl;
}

// alternate child2 constructor
child2::child2(int c) {
   cdata = c;
   cout << "child2 constructor " << cdata << endl;
}

child2::~child2() { 
   cout << "child2 destructor " << cdata << endl;
} 

void child2::print() { 
   cout << "child2 print " << cdata  << ":"; parent::print(); 
}
//resulting output
Child 1 default declaration
Parent constructor
child1 constructor 1
child1 print 1:Parent print P

Child 2 default declaration
Parent constructor
child2 constructor 2
child2 print 2:Parent print P

Child 2 override declaration
Parent constructor
child2 constructor 3
child2 print 3:Parent print P

Now the destructors begin

child2 destructor 3
Parent destructor
child2 destructor 2
Parent destructor
child1 destructor 1
Parent destructor