Part D - Inheritance

Derived Classes

Describe inheritance in terms of hierarchy
Introduce the terminology and syntax of derived classes
Describe protected access to the members of a class

"Public inheritance must always model 'is-a' ('works-like-a')" (Liskov, 1988)
"In correct inheritance, a derived class models a special case of a more general ... concept" (Sutter, Alexandrescu, 2005)

Hierarchy | Definition | Access | Summary | Exercises


The second principal feature of an object-oriented language alongside encapsulation is inheritance.  Inheritance refers to the relationship between classes where one class inherits the entire structure of the other.  The relationship is naturally hierarchical and the most highly coupled after friendship. 

This chapter introduces hierarchy, the terminology for describing inheritance and the syntax for defining a class that inherits the entire structure of another class.  This chapter also describes covers accessibility privileges between the classes in a hierarchy. 


Hierarchy

A comprehensive set of inheritance relationships is the Linnean Hierarchy in Biology (a small portion is shown below).  This hierarchy relates all of the biological species in existence to one another.  Starting from the bottom of the hierarchy, we see that a human is a homo, which is a hominidae, which is a primate, which is a mammal, which is a chordata, which is an animal.  Similarily a dog is a canis, which is a canidae, which is a carnivora, which is a mammal, which is a chordata, which is an animal.

linnean hierarchy

Carl Linnaeus earned himself the title of Father of Taxonomy after developing this hierarchy.  He grouped the genera of Biology into higher taxa based on shared similarities.  Using his taxa with its modern refinements, we say that the genus Homo, which includes the species sapiens, belongs to the Family Hominidae, which belongs to the Order Primates, which belongs to the Class Mammalia, which belongs to the Phylum Chordata, which belongs to the Kingdom Animalia.  For more details see the University of Michigan Museum of Zoology's Animal Diversity Site.

Inheritance is transitive.  A human inherits the structure of a homo, which inherits the structure of a hominoid, which inherits the structure of a primate, which inherits the structure of a mammal, which inherits the structure of a chordata, which inherits the structure of an animal. 

Inheritance is not commutative.  A primate is an animal, but an animal is not necessarily a primate: dogs and foxes are not primates.  Primates have highly developed hands and feet, shorter snouts and larger brains than dogs and foxes. 

Terminology

The relative position of two classes in a hierarchy reflects their inheritance relationship.  A class lower in the hierarchy is a kind of a class that is higher in the hierarchy.  For example, a dog is a kind of canis, a fox is a kind of vulpes and a human is a kind of homo.  In our retail store example from the first chapter, a SpecialOrder is a kind of Order

is a kind of hierarchy

The SpecialOrder class inherits the entire structure of the Order class. 

Base and Derived

We call the Order class the base class and the SpecialOrder class the derived class.  That is, a derived class is lower in the hierarchy, while the base class is higher in the hierarchy.  The derived class inherits the structure of its base class.  The derived class is the subject of the 'is-a-kind-of' expression, while the base class is the expression's object. 

We depict an inheritance relationship using an arrow drawn from the derived class to the base class:

inheritance arrow

We depict an instance of a derived class by drawing the instance variables of the derived class in the direction of increasing addresses with respect to the instance variables of its base class:

object representation

An instance of a derived class contains the instance variables of the base class and those of the derived class, while an instance of the base class only contains the instance variables of the base class. 

The Inherited Structure

A derived class contains all of the instance variables and all of the normal member functions of its base class.  Normal member functions exclude the constructors, the destructor, and the assignment operator.  We refer to constructors, destructors and assignment operators as special member functions.  The derived class does not inherit these special functions.


Definition

The definition of a derived class takes the form

class Derived : access Base {

    // ...

};

where Derived is the name of the derived class and Base is the name of the base class.  access identifies the access that member functions of the derived class have to the non-private members of the base class.  The default is private.  The most common access modifier is public

For example, a Student is a kind of Person.  A Person includes Students amongst other kinds of persons.  Every Person has a name.  Accordingly, let us derive our Student class from a Person class, where the base class contains a name. 

The header file contains the class definitions:

 // Student.h

 #include <iostream>
 const int N = 30;
 const int M  = 13;

 class Person {                   // Base Class - start
     char person[N+1];
   public:
     void set(const char* n);
     void displayName(std::ostream&) const;
 };                               // Base Class - end

 class Student : public Person { // Derived Class - start 
     int no;
     char grade[M+1];
   public:
     Student();
     Student(int, const char*);
     void display(std::ostream&) const;
 };                              // Derived Class - end

The implementation file contains the function definitons:

 // Student.cpp

 #include <cstring>
 #include "Student.h"

 void Person::set(const char* n) {
     std::strncpy(person, n, N);
     person[N] = '\0';
 }

 void Person::displayName(std::ostream& os) const { 
     os << person << ' ';
 }

 Student::Student() {
     no = 0;
     grade[0] = '\0';
 }

 Student::Student(int n, const char* g) {
     // see p.61 for validation logic
     no = n;
     std::strcpy(grade, g);
 }

 void Student::display(std::ostream& os) const {
     os << no << ' ' << grade;
 }

The following client program uses the implementation to produce the results on the right:

 // Derived Classes
 // derived.cpp

 #include <iostream>
 #include "Student.h"

 int main() {
     Student harry(975, "ABBAD");

     harry.set("Harry");       // inherited
     harry.displayName();      // inherited 
     harry.display(std::cout); // not inherited
     std::cout << std::endl;
 }











 Harry 975 ABBAD 


The main() function refers only to a Student object, without mention of the Person base class.  That is, the hierarchy itself is invisible to the client.  We can upgrade the hierarchy without altering the client code in any way whatsoever. 


Access

A derived class can have different levels of access to the members of the base class from which it inherits.  C++ supports three levels of access:

  • private - bars all access
  • protected - grants access to derived classes only
  • public - unlimited access

In the above example, the member functions of the Student class cannot access the data member of the Person class, because that member is private to the base class.  On the other hand, since the Person class has identified its two member functions as public, the main() function and the member functions of the Student class have access to them. 

Limiting Access to Derived Classes

The keyword protected limits access to member functions of derived classes. 

For example, let us limit access privileges for displayName() to derived classes.  This member function can no longer be called from main() we must call it directly from Student::display().  The header file limits the access:

 // Student.h

 #include <iostream>
 const int N = 30;
 const int M = 13;

 class Person {
     char person[N+1];
   public:
     void set(const char* n);
   protected:
     void displayName(std::ostream&) const; 
 };

 class Student : public Person {
     int no;
     char grade[M+1];
   public:
     Student();
     Student(int, const char*);
     void display(std::ostream&) const;
 };

The implementation file calls displayName() from directly from Student::display():

 // Student.cpp

 #include <cstring>
 #include "Student.h"

 void Person::set(const char* n) {
     std::strncpy(person, n, N); // validates length 
     person[N] = '\0';
 }

 void Person::displayName(std::ostream& os) const {
     os << person << ' ';
 }

 Student::Student() {
     no = 0;
     grade[0] = '\0';
 }

 Student::Student(int n, const char* g) {
     // see Current Object chapter for validation logic 
     no = n;
     std::strcpy(grade, g);
 }

 void Student::display(std::ostream& os) const {
     displayName(os);
     os << no << ' ' << grade;
 }

Note that we refer to displayName() directly without any scope resolution as if the member function is a member of our Student class. 

The output from the following client program is shown on the right:

 // Protected Access
 // protected.cpp

 #include <iostream>
 #include "Student.h"

 int main() {
     Student harry(975, "ABBAD");

     harry.set("Harry");
     harry.display(std::cout);
     std::cout << std::endl;
 }









 Harry 975 ABBAD


Avoid Granting Protected Access to Data Members

Granting data members protected access creates a security hole.  If a base class grants protected access to any of its data members, any member function of a derived class can circumvent any validation procedure in the base class.  If the base class in the above example had granted access to the person data member, we could change its contents from our Student class to a string of more than N characters, which would probably break our Student object. 

Since granting protected access to a data member exposes that member to potential corruption, it is considered poor design.  A protected read only query is the preferred alternative.


Summary

  • a derived class inherits the entire structure of its base class
  • the access modifier protected grants access to member functions of the derived class
  • any member function of a derived class may access any protected or public member of its base class
  • keeping a data member private and accessing it through a protected query is good design

Exercises




Previous Reading  Previous: Custom File Operators Next: Functions in a Hierarchy   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo
Creative Commons License