21. Inheritance

1. Introduction

Inheritance provides a mechanism to build new, improved, classes using already made classes. The fundamental principle goes like this: A new, derived class, keeps the methods and attributes from the old (base) class, and then gets some more attributes and more methods.

Inheritance makes it possible to build modular software. Complex software is often divided into small independent pieces called modules. Individual modules can then be used as building blocks in different software projects. You have probably written some functions only once. If you wrote them really well and made them easy to call, then you were able to use these same functions in other programs. Functions that sort sequences are good examples of functions that can be re-used in many programs.

It is often possible to insert exact same classes into different projects. For example, a class that implements fractions can be programmed only once. The same class can then be used in a variety of programs in number theory and numerical mathematics.

However, it is not often easy to design new programs in such a way that old classes can be used without any modifications. Inheritance is helpful when new classes need some additional features that the old classes did not have.

2. Illustration of inheritance: polynomials

We will use one example to learn about inheritance. This example will teach us the procedures and syntax. The example will be very quick at achieving this goal. However, this example is not a good at teaching the art of designing software with inheritance. Building modular software is beyond the scope of these notes.

We will first create a class that models linear functions. We will call it LinearFunction. Then we will create the class QuadraticFunction using inheritance. We will be able to re-use several components of the class LinearFunction.

2.1. Linear function

Let us assume that we figured out how to make a class that stores coefficients of linear functions of the type \(f(x)=bx+a\) and makes the evaluations of the function for a diverse set of input values \(x\). The following code has the implementation of the class and a main function that uses the class.

#include<iostream>
class LinearFunction{
private:
   double a;
   double b;
public:
   void set_a(double );
   void set_b(double );
   double get_a() const;
   double get_b() const;
   double evaluate(double ) const;
};
void LinearFunction::set_a(double x){ a = x;}
void LinearFunction::set_b(double x){ b = x;}
double LinearFunction::get_a() const{return a;}
double LinearFunction::get_b() const{return b;}
double LinearFunction::evaluate(double x) const{
   return b*x+a;
}
int main(){
   LinearFunction lf; 
   lf.set_a(5); lf.set_b(9);
   std::cout<<lf.evaluate(10)<<"\n";
   return 0;
}

2.2. Quadratic function

The quadratic function has the form \(g(x)=cx^2+bx+a\), where \(c\), \(b\), and \(a\) are real numbers. We will now build the class that inherits from LinearFunction. The inheritance is triggered with the syntax

class QuadraticFunction : public LinearFunction{ 
// ... 
};

We will explain the word public later in section 6.

Terminology.
  • The class LinearFunction is called base class or parent class.
  • The class QuadraticFunction is called derived class or child class.

The class QuadraticFunction now has all the attributes and all the methods that LinearFunction has. It does not have a direct access to the attributes a and b because they were private in LinearFunction. However, the methods get_a, get_b, set_a, and set_b are public. These methods provide all the access we need.

We need to add another attribute c and the methods get_c and set_c. We will also override the method evaluate. When evaluate is called on an object of the class QuadraticEquation a different formula will be used.

#include<iostream>
class LinearFunction{
private:
   double a;
   double b;
public:
   void set_a(double );
   void set_b(double );
   double get_a() const;
   double get_b() const;
   double evaluate(double ) const;
};
void LinearFunction::set_a(double x){ a = x;}
void LinearFunction::set_b(double x){ b = x;}
double LinearFunction::get_a() const{return a;}
double LinearFunction::get_b() const{return b;}
double LinearFunction::evaluate(double x) const{
   return b*x+a;
}
class QuadraticFunction : public LinearFunction{
private:
   double c; 
public: 
   void set_c(double );
   double get_c() const;
   double evaluate(double ) const;
};
void QuadraticFunction::set_c(double x){ c = x;}
double QuadraticFunction::get_c() const{return c;}
double QuadraticFunction::evaluate(double x) const{
   return c*x*x+get_b()*x+get_a();
}
int main(){
   QuadraticFunction qf; 
   qf.set_a(5); qf.set_b(9); qf.set_c(2);
   std::cout<<qf.evaluate(10)<<"\n";
   return 0;
}

3. Using pointers to base class to access derived class. Virtual override

The pointer of type LinearFunction* can be used to store the address of an object of QuadraticFunction. Later, you will see examples where this feature is very useful. However, we will first see some troubles that need to be corrected.

Let us assume that the classes LinearFunction and QuadraticFunction are defined as above and let us consider the following main() function.

int main(){
   QuadraticFunction qf;
   LinearFunction* aQ;
   qf.set_a(5); qf.set_b(9); qf.set_c(2);
   aQ=&qf;
   std::cout<<aQ->evaluate(10)<<"\n";
   return 0;
}

The pointer aQ is of type LinearFunction*. Although it is declared as a pointer to the base class, aQ is allowed to contain an address of an object of the derived class. The assignment aQ=&qf; would correctly assign the address of qf to the variable aQ.

However, the evaluation aQ->evaluate(10) causes trouble. It calls the wrong method. It calls the method from the base class instead of the method from the derived class.

The method evaluate in the derived class is an override of the method from the base class. The base class must contain the word virtual in front of the name of the method. Then it will be able to route the function calls correctly when the methods are accessed from pointers. We will now list the improved code.

#include<iostream>
class LinearFunction{
private:
   double a;
   double b;
public:
   void set_a(double );
   void set_b(double );
   double get_a() const;
   double get_b() const;
   virtual double evaluate(double ) const;
};
void LinearFunction::set_a(double x){ a = x;}
void LinearFunction::set_b(double x){ b = x;}
double LinearFunction::get_a() const{return a;}
double LinearFunction::get_b() const{return b;}
double LinearFunction::evaluate(double x) const{
   return b*x+a;
}
class QuadraticFunction : public LinearFunction{
private:
   double c; 
public: 
   void set_c(double );
   double get_c() const;
   double evaluate(double ) const;
};
void QuadraticFunction::set_c(double x){ c = x;}
double QuadraticFunction::get_c() const{return c;}
double QuadraticFunction::evaluate(double x) const{
   return c*x*x+get_b()*x+get_a();
}
int main(){
   QuadraticFunction qf;
   LinearFunction* aQ=&qf;
   qf.set_a(5); qf.set_b(9); qf.set_c(2); 
   std::cout<<aQ->evaluate(10)<<"\n";
   return 0;
}
Problem 1. What does the following code print?
#include<iostream>
class A{
public:
   virtual long fun1() const;
   long fun2() const;
};
long A::fun1() const{return 1;}
long A::fun2() const{return 2;}
class B : public A{
public:
   long fun1() const;
   virtual long fun2() const;
};
long B::fun1() const{return 3;}
long B::fun2() const{return 4;}
int main(){
   A* a=new B;
   B* b=new B;
   long result = 1000*a->fun1()+ 100*a->fun2();
   result+=10*b->fun1()+b->fun2();
   std::cout<<result<<"\n";
   delete a; delete b;
   return 0;
}

Problem 2. Why does the following program print 1?
#include<iostream>
class A{
public:
   virtual long fun(long = 0) const;
};
long A::fun(long x) const{return 1;}
class B : public A{
public:
   long fun(double = 0) const;
};
long B::fun(double x) const{return 2;}
int main(){
   B b;
   A* p=&b;
   std::cout<<p->fun()<<"\n";
   return 0;
}

The pointers to base class cannot be used to access all of the methods from the derived class. They can only access the overriden methods, and only if they are virtual. Assume that we have previously defined classes LinearFunction and QuadraticFunction. Assume that the object qf and the pointer aQ are defined with:

QuadraticFunction qf;
LinearFunction* aQ=&qf;

We are not allowed to give the command aQ->set_c(2);. The pointer aQ if of type LinearFunction*. It can only see the methods of the class LinearFunction. However, if aQ->evaluate is called, then aQ will realize that this is a virtual method, and it will search for the implementation outside of LinearFunction.

4. Virtual destructors

If the class requires the implementation of the destructor, and if there is a possibility that the class could be a base in an inheritance, then the destructor should be virtual.

The declaration would look like this:

class A{
// ???
public:
   virtual ~A();
};

5. Protected members

We had a minor inconvenience while implementing QuadraticFunction. We were not able to access the coefficients a and b directly. The coefficients a and b are private members of LinearFunction. Only the methods of LinearFunction are allowed to access these private members.

It is possible to make an exception. We can allow the methods of derived classes to access the attributes a and b, and still prohibit everybody else from accessing a and b. To achieve such outcome, we need to use the word protected instead of private.

Protected members of the class can be accessed by the class and the derived classes.

The modified code in which we use the keyword protected is listed below.

#include<iostream>
class LinearFunction{
protected:
   double a;
   double b;
public:
   void set_a(double );
   void set_b(double );
   double get_a() const;
   double get_b() const;
   virtual double evaluate(double ) const;
};
void LinearFunction::set_a(double x){ a = x;}
void LinearFunction::set_b(double x){ b = x;}
double LinearFunction::get_a() const{return a;}
double LinearFunction::get_b() const{return b;}
double LinearFunction::evaluate(double x) const{
   return b*x+a;
}
class QuadraticFunction : public LinearFunction{
private:
   double c; 
public: 
   void set_c(double );
   double get_c() const;
   double evaluate(double ) const;
};
void QuadraticFunction::set_c(double x){ c = x;}
double QuadraticFunction::get_c() const{return c;}
double QuadraticFunction::evaluate(double x) const{
   return c*x*x+b*x+a;
}

The method QuadraticFunction::evaluate can now use the variables a and b. Before we had to use get_a() and get_b() instead.

6. Types of inheritance

We constructed QuadraticFunction using public inheritance from LinearFunction. There are two more types of inheritance: private and protected. The type of inheritance changes how the methods of the base class will be accessed by the objects and descendants of the derived class.

Let us look at the following example of the base class, and three classes derived from the base class.

class Base{
private:
   int a;
protected:
   int b;
public:
   int c;
};
class DerivedA : private Base{ /* ... */};
class DerivedB : protected Base{ /* ... */};
class DerivedC : public Base{ /* ... */};

6.1. Private members of the base class

The attribute a is private in Base. This attribute is not accessible from any of the classes DerivedA, DerivedB, and DerivedC.

6.2. Protected members of the base class

The attribute b is protected in Base. It will be accessible from each of DerivedA, DerivedB, and DerivedC. It will be assumed to have status protected in each of the classes DerivedB and DerivedC. However, it will become private in class DerivedA.

6.3. Public members of the base class

The attribute c is public in Base. It will remain public in DerivedC. It will turn into protected in DerivedB. It will turn into private in DerivedA.

6.4. Table with summary

The types of inheritance are summarized in the table below. Each row follows the destiny of a member of the base class. The 0-th row lists the types of inheritance. The row 1 covers the destiny of a private member of the base class. The row 2 follows a protected member of the base class. The row 3 follows a public member of the base class.

\begin{eqnarray*} \begin{array}{|c||c|c|c|}\hline \mbox{member}\setminus \mbox{inheritance} & \mbox{private} & \mbox{protected} & \mbox{public}\\ \hline \hline \mbox{private} & \mbox{not accessible} &\mbox{not accessible} & \mbox{not accessible}\\ \hline \mbox{protected} & \mbox{private} & \mbox{protected} & \mbox{protected}\\ \hline \mbox{public} & \mbox{private} & \mbox{protected} & \mbox{public}\\ \hline \end{array} \end{eqnarray*}

The following link from stackoverflow has a discussion with multiple ways of summarizing the types of inheritance: https://stackoverflow.com/questions/860339/what-is-the-difference-between-public-private-and-protected-inheritance-in-c.

7. Avoid protected members

The keyword protected should be avoided as much as possible. It breaks the encapsulation. When you are designing a class you had your reasons for guarding the private members. You did not want to expose them to the outside world. The private section is amazing at guarding the vulnerable pieces of your code.

The protected section does not actually offer much of a protection. Anyone can now access them. All they need to do is to pretend to be nice people, make a derived class, and use that derived class to access the protected members.

There is a problem though, it is very tempting to convert private to protected in late stages of the code. The world of code is full of protected members. Almost every programmer at some point decided to save time and allowed the keyword protected to find its way into the code.

8. Practice problems

Problem 3. Create the class Product whose attributes contain the information about production date, country of origin and price. Create a class Food that inherits from product and also contains the additional attribute the expiration date.

Problem 4.

The first stage of the program consists of a user providing different functions through the user input. The user repeatedly chooses one of the options 1, 2, 4, and 5. When the user chooses 5, this is the end of the function input phase of the program.

  • 1. Linear function. The user is asked for real numbers \(k\) and \(b\) and the name of the function. The name is of type string. The function to be stored under the given name is \(y=kx+b\).
  • 2. Reciprocal of a quadratic function. The user is asked for positive real numbers \(p\) and \(q\) and the name of the function. The name is of type string. The function to be stored under the given name is \(y=\frac1{px^2+q}\).
  • 4. Reciprocal of a bi-quadratic function. The user is asked for positive real numbers \(a\) and \(c\), a non-negative real number \(b\), and the name of the function. The name is of type string. The function to be stored under the given name is \(y=\frac1{ax^4+bx^2+c}\).
  • 5. End of input of functions.

In the second phase the user inputs a positive integer \(n\) and the sequence of \(n\) lines of text. Each line contains the name of the function and the argument of the function. The program should evaluate the functions at the given arguments, add up the results of all evaluations and print the result on the standard output.

Example:

Input: 
4 0.5 0.0 1.0 apple
2 0.01 0.01 grape
4 0.01 0.0 0.5 watermelon
1 7.0 -2.0 mouse
1 -2.0 -5.0 dog
5
3
grape 2.0
watermelon 5.0
mouse 5.0

Output:

53.1481