Object-oriented Software in C++

Chapter 9 Inheritance.


In converting this chapter on inheritance to HTML from the original Microsoft Word file, much of the original layout has been lost and thus this document can only hint at the true format of the original.
These pages use HTML version 3 (Use Netscape 1.1 or later)

Introduction

The following chapter uses the class Account, which is defined in chapter 4 as:

Specification Implementation
class Account {
public:  Account();
  float account_balance();
  float withdraw( float );
  void deposit( float );
private:
  float  the_balance;
};
 
Account::Account()
{
  the_balance = 0.0;
}

float Account::account_balance()
{
  return the_balance;
}

float Account::withdraw(float money)
{
  if ( money <= the_balance )
  {
     the_balance = the_balance-money;
     return money;
  } else {
    return 0;
  }
}

void Account::deposit( float
money )
{
  the_balance = the_balance + money;
}


9 Inheritance

This chapter introduces a way of re-using existing classes. This is done via a mechanism which allows a new class to be created out of existing classes. This new class is said to be derived from the existing base classes.

INDEX 9.1 A savings account

Having already created a class to deal with a bank account Account, it is appropriate to think about how an interest-bearing account may be defined. This new account will pay interest on the current balance held in the account at the end of each day.

Rather than create a totally new class, the concept of inheritance can be used. A class is declared which inherits all the data and methods of Account, and adds new data items and methods to facilitate the operation of an interest-bearing account.
An interest-bearing account is the same as an ordinary account but includes ways of specifying the daily interest rate and the actions of accumulating the daily interest and adding this to the outstanding balance at the end of the accounting period.

In the example below, the default annual interest rate on the account is 10%, which on a daily basis represents 0.026116%.

The specification for this class is:

class Interest_Account : public Account {
public:
  Interest_Account();
  static void prelude( const float );// Set up interest rate
  void calc_interest(void);       // calculate the interest
  void add_interest(void);        // Add the interest to account
protected:
  void accumulate_interest(const float); // total
private:
  float the_accumulated_interest;  // Interest gained
  static float the_interest_rate;  // interest rate
};

The role of a protected member of a class will be discussed fully later in this chapter in section 9.4. Essentially protected restricts access of the member function to only other member functions of the class.

The class Interest_Account has the following methods:

In the class Interest_Account Inherited from Account
add_interest
calc_interest
Interest_Account
accumulate_interest
Account
account_balance
withdraw
deposit
prelude

and data items:

In the class Interest_Account Inherited from Account
 the_accumulated_interest
 the_interest_rate
 
 the_balance
 

As can be seen from the above new class Interest_Account this mechanism saves considerable time and effort, as code from a previously defined class can be re-used to make a new class.

However, this re-use is conditional on existing classes performing suitable operations.

Terminology

Figure 9.1 illustrates the syntax for deriving a new class from a base class.

Figure 9.1 Interest_Account derived from Account.

Note: The scope modification to the members of the base class will be discussed in detail in section 9.4.1. In this case it makes all the non-private members of Account visible to an implementor and user of the class Interest_Account.

The implementation part of this new class is as follows:

const float DAILY_RATE = 0.00026116;       // 10%

float Interest_Account::the_interest_rate; // Declare storage

Interest_Account::Interest_Account()
{
  the_accumulated_interest = 0.0;
}

void Interest_Account::prelude( const float ir )
{
  the_interest_rate = ir;
}

The member function prelude is responsible for initializing the static member of the class. Remember, if a data member of a class is declared as static, then only one copy of this data item exists which is shared by all the instances of the class.

The member functions add_interest, accumulate_interest and calc_interest are responsible for processing the interest on the account.

At the end of each working day calc_interest will be called and will accumulate the interest in the member variable the_accumulated_interest. This will be deposited into the customer's account at the end of the accounting period using the function add_interest.

void Interest_Account::calc_interest(void)
{
  accumulate_interest(
      account_balance() * the_interest_rate );
}

void Interest_Account::accumulate_interest(const float ai )
{
  the_accumulated_interest += ai;
}

void Interest_Account::add_interest(void)
{
  deposit( the_accumulated_interest );
  the_accumulated_interest = 0.0;
}

Each instance of Interest_Account will contain the member variable the_balance and the_accumulated_interest.

INDEX 9.2 Call of a constructor in the base class

The constructor for an object of type Interest_Account will use the constructor for Account to set up the initial balance in the account. This does not have to be explicitly called if the constructor for the base class has no parameters.

Interest_Account::Interest_Account()
{
  the_accumulated_interest = 0.0;
}

Note: If the constructor for Account had a parameter, then the constructor for Interest_Account would have been specified in a slightly different way to allow a parameter to be passed to the constructor of Account.

*9.2.1 Order of calling constructors and destructors

The constructor in the base class is called first, followed by the constructor in the derived class. When the object's storage is released the destructor in the derived class will be called first, followed by the destructor in the base class.

*9.2.2 Putting it all together

The following code uses the class Interest_Account:

void process(void);

void main()
{
  const float DAILY_RATE = 0.00026116;    // 10% Annual rate
  Interest_Account::prelude(DAILY_RATE);
  cout << setiosflags( ios::fixed );
  cout << setiosflags( ios::showpoint );
  cout << setprecision(2);
  process();
}

The function process then tests the class Interest_Account:

void process()
{
  Interest_Account  mike;
  float obtained;
  mike.deposit(1000);
  cout << "mike Balance = " << mike.account_balance() << "\n";
  obtained = mike.withdraw(200);
  cout << "mike has withdrawn " << obtained << "\n";
  cout << "mike Balance = " << mike.account_balance() << "\n";
  mike.deposit(50);
  cout << "mike Balance = " << mike.account_balance() << "\n";
  mike.calc_interest();
  mike.add_interest();
  cout << "mike Balance = " << mike.account_balance() << "\n";
}

and produces the following output:

mike Balance = 1000.00
mike has withdrawn 200.00
mike Balance = 800.00
mike Balance = 850.00
mike Balance = 850.22

Note: As the bank account is held as a floating point number, the actual amount held may well not be exact to two decimal places.

The class Interest_Account has the relationship 'is a' to the class Account. Hence an object of type Interest_Account may be used as if it were an object of type Account. Here the inheritance mechanism has been used to produce a specialization of Account.

INDEX 9.3 A saving account with tiered interest rates

A class which has been derived from a base class may itself be used as a base class to derive other classes. For example, the bank may wish to introduce a savings account with a tiered rate of interest. With this type of account the more money that is saved, the higher the interest paid on the account. The base interest rate offered, however, is the same as an Interest_Account.

Amount in account Annual interest rate Daily interest rate
(365 days in year)
less than £10000 10% 0.026116%
£10000 - £24999.99 11% 0.028596%
£25000+ 12% 0.031054%

The specification for the class would be:

class  Special_Interest_Account : public Interest_Account {
public:
  Special_Interest_Account();
  void static prelude( const float, const float, const float );
  void calc_interest(void);
private:
  static float the_interest_rate1;
  static float the_interest_rate2;
  static float the_interest_rate3;
};

while the implementation of the member functions would be:

const float DAILY_RATE_R1 = 0.00026116;    // 10%
const float DAILY_RATE_R2 = 0.00028596;    // 11%
const float DAILY_RATE_R3 = 0.00031054;    // 12%

float Special_Interest_Account::the_interest_rate1;// The storage
float Special_Interest_Account::the_interest_rate2;// The storage
float Special_Interest_Account::the_interest_rate3;// The storage

Special_Interest_Account::Special_Interest_Account() {}

void Special_Interest_Account::prelude
  (const float ir1, const float ir2, const float ir3)
{
  the_interest_rate1 = ir1;
  the_interest_rate2 = ir2;
  the_interest_rate3 = ir3;
}

void Special_Interest_Account::calc_interest(void)
{
  float money =  account_balance();
  if ( money < 10000 )
    accumulate_interest( account_balance()*the_interest_rate1 );
  else if ( money < 25000 )
    accumulate_interest( account_balance()*the_interest_rate2 );
  else
    accumulate_interest( account_balance()*the_interest_rate3 );
}

The member function calc_interest overloads the function calc_interest in the class Interest_Account to provide the extra functionality. The effect of this is that an instance of the class Special_Interest_Account cannot be used where an object of type Interest_Account is required. Though the inheritance mechanism is used to produce a specialization of Interest_Account it does not have the relationship 'is a' to Interest_Account.

If the member function calc_interest had to call the member function calc_interest in the class Interest_Account then the scope resolution operator :: would be used as follows:

Interest_Account::calc_interest()

to distinguish between the two functions calc_interest.

*9.4.1 Putting it all together

The following code uses the class Special_Interest_Account:

void process( void );

void main()
{
  const float DAILY_RATE_R1 = 0.00026116;  // 10% Annual rate
  const float DAILY_RATE_R2 = 0.00028596;  // 11% Annual rate
  const float DAILY_RATE_R3 = 0.00031054;  // 12% Annual rate
  Special_Interest_Account::prelude(
    DAILY_RATE_R1, DAILY_RATE_R2, DAILY_RATE_R3 );
  process();
}

void process()
{
  Special_Interest_Account        mike;
  float obtained;
  cout << setiosflags( ios::showpoint );
  cout << setiosflags( ios::fixed );
  cout << setprecision(2);
  cout << "mike Balance = " << mike.account_balance() << "\n";
  mike.deposit(20000);
  cout << "mike Balance = " << mike.account_balance() << "\n";
  obtained = mike.withdraw(2000);
  cout << "mike has withdrawn " << obtained << "\n";
  cout << "mike Balance = " << mike.account_balance() << "\n";
  mike.deposit(50);
  cout << "mike Balance = " << mike.account_balance() << "\n";
  mike.calc_interest();
  mike.add_interest();
  cout << "mike Balance = " << mike.account_balance() << "\n";
}

and produces the following output:

mike Balance = 0.00
mike Balance = 20000.00
mike has withdrawn 2000.00
mike Balance = 18000.00
mike Balance = 18050.00
mike Balance = 18055.16

Inheritance can cut down considerably the amount of code that has to be written for an application but, to use inheritance effectively, careful planning is required!

INDEX 9.4 Visibility of class members

The visibility of a member of a class to the outside world, and to other classes which may inherit the class, depends on how it has been declared in the class. In essence there is a hierarchy of visibility.

class Account {

public:

protected:

private:

}

The following table summarizes the visibility of class members. For each case, possible items that may be described in this way are suggested.

Member of class declared as Visibility of class member Examples of items which might be declared in this way
Public To all items within scope of this class. Functions visible to a user of the class.
Protected To another class member and any class member of a class which inherits this class publicly. Functions which are used to build the functions visible to a user of the class and would be useful to a derived class.
Private Only to another class member. Variables, and any functions which should not be used even by a derived class.

Note: Items are by default considered private until a visibility label is met.

*9.4.1 Visibility modifiers

This visibility of base class members can be changed, by use of a scope modification when the derived class is declared. Shown below is the effect of scope modification on the visibility of base class members in a derived class.

If a new class Derived had been described as:

class Derived : public Base {

then the access to the members of Base in the class Derived would be as follows:

If a new class Derived_2 had been described as:

class Derived_2 : private Base {

then the access to the members of Base in the class Derived_2 would be as follows:

Note: It is good programming practice to restrict the scope or visibility of items as far as possible.

INDEX 9.5 Constructors and destructors

As seen previously a constructor for a class will be called, if one has been specified when an instance of that class is created.

A destructor may also be specified which will be called just before the instance of the class variable goes out of scope and any storage is released back to the system. An instance of a class will usually go out of scope when the block in which it has been declared is exited.

For example, the specification of a room might be as follows:

class Room {
public:
  Room(const int);  // Constructor
  ~Room();          // Destructor
                    // Other member functions
private:
                    // member variables
};

It might be used in a program as follows:

watts_building()
{
  Room w422(50);    // declare an instance of Room
}

The constructor for the class Room is called when the room w422 is declared; the constructor in this case takes a mandatory parameter that describes the number of square metres of office space.

The destructor ~Room is called when the block containing an instance of the class variable is exited.

INDEX 9.6 A class to describe a room

This demonstration class describes a room in a building. The class contains the size of the room in square metres. A member function is provided to list this information.
The constructor initializes an instance of room with its size. The destructor ~Room simply records the fact that the instance of room has gone out of scope and has hence been destroyed.

The specification of the class is:

class Room {
public:
  Room(const int);
  ~Room();
  void size(void);        // display size of room
private:
  int the_size_sq_metres; // Size in square metres
};

The implementation would be:

Room::Room(const int n)
{
  the_size_sq_metres = n;
  cout << "Constructor Room";
  cout << " size in square metres = ";
  cout << n << "\n";
}
Room::~Room()
{
  cout << "Destructor Room" << "\n";
}
void Room::size(void)
{
  cout << "Method Room::size()";
  cout << " size in square metres = ";
  cout << the_size_sq_metres << "\n";
}

*9.6.1 Putting it all together

The above class description could be used in a program which contains the following function.

void proc_room()
{
  Room w422(50);   w422.size();
}

The output from the program when this function is called would be:

Constructor Room size in square metres = 50
Method Room::size() size in square metres = 50
Destructor Room

INDEX 9.7 A class to describe an office

This demonstration class is derived from the class Room and describes an office in a building. The class Office is initialized with the size of the office and the number of staff members who can be accommodated.

The specification for the class is:

class Office : public Room {
public:
  Office( const int , const int );
  ~Office();
  void staff(void); // Display number of staff in room
private:
  int the_no_staff; // The number of staff
};

The implementation for the class would be:

Office::Office( const int size, const int no_staff) : Room(size)
{
  the_no_staff = no_staff;
  cout << "Constructor Office";
  cout << " the number of staff = " << the_no_staff << "\n";
}
Office::~Office()
{
  cout << "Destructor Office" << "\n";
}
void Office::staff(void)
{
  cout << "Method Office::staff()";
  cout << " number of staff = " << the_no_staff << "\n";
}

The constructor for Office cannot in this case implicitly call the constructor for Room, as the size of the room has to be specified as a parameter to the constructor. This is called explicitly as illustrated in Figure 9.2.

Figure 9.2 Call of a constructor explicitly in the base class.

*9.7.1 Putting it all together

The above class description could be used in a program which contains the following function.

void proc_office()
{
  Office w418(24,2); w418.staff(); w418.size();
}

The output from the program when this function was called would be as follows:

Constructor Room size in square metres = 24
Constructor Office the number of staff = 2
Method Office::staff() number of staff = 2
Method Room::size() size in square metres = 24
Destructor Office
Destructor Room

Note: The order in which the constructors and destructors are called for a class which has derived components from a base class.

INDEX 9.8 Multiple inheritance

So far a new class has been created for a single existing class, which itself may have been created from another class. In some situations, what is required is to create a new class based on two or more existing classes. This concept is often referred to as multiple inheritance.

As an example, consider the class Interest_Account which provides a class for representing an interest-bearing account. The name of the account holder is not specified as part of the account. A new class Named_Account can be created from the existing classes Interest_Account and Name_Address which will allow the following operations on an instance variable.

The specification for this class is:

class Named_Account:public Interest_Account, public Name_Address
{
public:
  Named_Account(const char name[] = "", const float = 0.0);
  void mini_statement() const;
};

The member functions are:

From the class
Interest_account
From the class
Name_Address
In the class
Named_Account
Account
account_balance
accumulate_interest
add_interest
calc_interest
deposit
Interest_Account
prelude
withdraw
Name_Address
print_address
print_nameset
Named_Account
mini_statement

The member variables are:

From the class
Interest_account
From the class
Name_Address
In the class
Named_Account
the_interest_rate
the_accumulated_interest
the_balance
the_string -

The implementation of the class is:

Named_Account::Named_Account(const char name[],const float amount)
  : Name_Address(name) , Interest_Account()
{
  deposit( amount );  // set account to initial amount
}

void Named_Account::mini_statement() const
{
  cout << "Mini Statement for ";
  print_name();
  cout << " Balance of account is  £" << account_balance();
  cout << "\n";
}

The only new code is the member function mini_statement which will print the name of the account holder followed by the current balance of their account. This uses the member function print_name of the class Name_Address.

The relationship between the classes is illustrated in Figure 9.3

Figure 9.3 Class hierarchy for Named_Account.

If classes are constructed carefully, then the possibility for re-use is considerably enhanced. However, there will be cases when the class does not do exactly what is required, in which case a re-casting of the base class may be the only answer.

For an object of type Named_Account the order of calling the base class's constructors is shown in Figure 9.4.

Figure 9.4 Order of calling constructors.

The body of Named_Account constructor deposits amount into the account, by calling the member function deposit of the class Account.

Note: The constructors are called from left to right.

If the constructors are not explicitly stated, then the order used for calling the constructors for the base classes, is the order in which the compiler processes the base class definitions.

*9.8.1 Putting it all together

The class Named_Account can now be used in a program as follows:

void main()
{
  const double DAILY_RATE = 0.00026116;    // 10% Annual rate
  Named_Account::prelude(DAILY_RATE);
  cout << setiosflags( ios::showpoint );
  cout << setiosflags( ios::fixed );
  cout << setprecision(2);
  process();                      // prototype omitted
}

void process()
{
  Named_Account   a_n_other( "A N Other/Brighton" );
  a_n_other.mini_statement();
  a_n_other.deposit(500);
  cout << "Deposit £500" << "\n";
  a_n_other.mini_statement();
  a_n_other.calc_interest();
  a_n_other.add_interest();
  cout << "Add interest at end of day" << "\n";
  a_n_other.mini_statement();
}

Note: The use of the prelude to initialize the variable containing the interest rate payable on the account.

When run, the program would produce the following results:

Mini Statement for A N Other Balance of account is  £0.00
Deposit £500
Mini Statement for A N Other Balance of account is  £500.00
Add interest at end of day
Mini Statement for A N Other Balance of account is  £500.13

INDEX 9.9 Static binding

In the examples shown so far, the binding between the call of a member function in an object and the code that is eventually executed, is evaluated at compile time. This is commonly referred to as static binding. The implication of this is that the programmer knows in advance what object the member function is called on.

For example, it would not be possible to deal with an array of bank accounts which are composed of different types of account.

Later in Chapter 11, a mechanism for dealing with an array of bank accounts of different type will be shown.

INDEX 9.10 Inherited functions

The table below summarizes which functions are inherited and which are not.

Type of function Inherited Must be a Created by default
The constructor No Member function Yes
The destructor No Member function Yes
Member function(s) Yes - No
Friend function(s) No - No

Note: Friend functions are described in Chapter 10.

INDEX 9.11 Inheritance of the same base class

Using multiple inheritance can sometimes produce a class made from multiple copies of the same base class. To prevent this from happening, the keyword virtual, can be prefixed to the name of the base class.

In the examples below, two base classes C1 and C2 have the specification and implementation.

Class C1: Class C2:
class C1
{
  public:
  C1();  // C1's interface
};

C1::C1() { cout << "C1 "; }
class C2
{
  public:
  C2();  // C2's interface
};

C2::C2() { cout << "C2 "; }

These base classes are used in the construction of two derived classes D1 and D2.

Class D1: Class D2:
class D1: virtual public C1,
          virtual public C2
{
public:
  D1();

  // D1's interface
};

D1::D1() { cout << "D1 "; }
class D2: virtual public C1
{
public:
  D2();

  // D2's interface
};

D2::D2() { cout << "D2 "; }

Note: The keyword virtual is used as a prefix when the class C1 or the class C2 are used in the derivation of the new classes.

At a later stage the classes D1, D2 and C2 are used to derive a new class E1 which will only contain a single copy of the classes C1 and C2.

class E1 : public D1, public D2, virtual public C2
{
public:
  E1();
  // Public interface of E1
};

E1::E1() { cout << "E1 "; }

The class hierarchy for the above classes is:

If the class E1 were used in the program:

void main()
{
  E1 object;
}

the output would be:

C1 C2 D1 D2 E1

Note: The order of calling the constructors is a simple left to right evaluation as specified in the derived class.

E1 -> D1 D2 C2# then E1 constructor

D1 -> C1 C2 then D1 constructor

D2 -> C1# then D2 constructor

# Class not included

Had the virtual keyword not been used, then multiple copies of the classes C1 and C2 would have been included. The results from running the program modified to exclude the keyword virtual would be:

C1 D1 C1 C2 D2 C2 E1

Note
The omission of virtual before the class
C2 in the derivation of E1 would have resulted in an extra copy of the class C2 being included.


© M.A.Smith University of Brighton. Created August 1995 last modified March 1997.
Comments, suggestions, etc. M.A.Smith@brighton.ac.uk * [Home page]