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.

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.

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:

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:

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
|