In This Chapter
Defining inheritance
Inheriting a base class
Constructing the base class
Exploring meaningful relationships: The IS_A versus the HAS_A relationship
This chapter discusses inheritance, the ability of one class to inherit capabilities or properties from another class.
Inheritance is a common concept. I am a human (except when I first wake up in the morning). I inherit certain properties from the class Human
, such as my ability to converse (more or less) intelligently and my dependence on air, water, and carbohydrate-based nourishment (a little too dependent on the latter, I'm afraid). These properties are not unique to humans. The class Human
inherits the dependencies on air, water, and nourishment from the class Mammal
, which inherited it from the class Animal
.
The capability of passing down properties is a powerful one. It enables you to describe things in an economical way. For example, if my son asks, 'What's a duck?' I can say, 'It's a bird that goes quack.' Despite what you may think, that answer conveys a considerable amount of information. He knows what a bird is, and now he knows all those same things about a duck plus the duck's additional property of 'quackness.' (Refer to Chapter 11 for a further discussion of this and other profound observations.)
Object-oriented (OO) languages express this inheritance relationship by allowing one class to inherit from another. Thus, OO languages can generate a model that's closer to the real world (remember that real-world stuff!) than the model generated by languages that don't support inheritance.
C++ allows one class to inherit another class as follows:
class Student { }; class GraduateStudent : public Student { };
Here, a GraduateStudent
inherits all the members of Student
. Thus, a GraduateStudent
IS_A Student
. (The capitalization of IS_A stresses the importance of this relationship.) Of course, GraduateStudent
may also contain other members that are unique to a GraduateStudent
.
Inheritance was introduced into C++ for several reasons. Of course, the major reason is the capability of expressing the inheritance relationship. (I'll return to that in a moment.) A minor reason is to reduce the amount of typing. Suppose that you have a class Student
, and you're asked to add a new class called GraduateStudent
. Inheritance can drastically reduce the number of things you have to put in the class. All you really need in the class GraduateStudent
are things that describe the differences between students and graduate students.
Another minor side effect has to do with software modification. Suppose you inherit from some existing class. Later, you find that the base class doesn't do exactly what the subclass needs. Or perhaps the class has a bug. Modifying the base class might break any code that uses that base class. Creating and using a new subclass that overloads the incorrect feature solves your problem without causing someone else further problems.
Here's the GraduateStudent
example filled out into a program InheritanceExample:
// // InheritanceExample - demonstrate an inheritance // relationship in which the subclass // constructor passes argument information // to the constructor in the base class // #include <cstdio> #include <cstdlib> #include <iostream>
using namespace std; class Advisor {}; // define an empty class class Student { public: Student(const char *pName = "no name") : name(pName), average(0.0), semesterHours(0) { cout << "Constructing student " << name << endl; } void addCourse(int hours, float grade) { cout << "Adding grade to " << name << endl; average = semesterHours * average + grade; semesterHours += hours; average = average / semesterHours; } int hours( ) { return semesterHours;} float gpa( ) { return average;} protected: string name; double average; int semesterHours; }; class GraduateStudent : public Student { public: GraduateStudent(const char *pName, Advisor adv, double qG = 0.0) : Student(pName), advisor(adv), qualifierGrade(qG) { cout << "Constructing graduate student " << pName << endl; } double qualifier( ) { return qualifierGrade; } protected: Advisor advisor; double qualifierGrade; }; int main(int nNumberofArgs, char* pszArgs[]) { // create a dummy advisor to give to GraduateStudent Advisor adv; // create two Student types Student llu("Cy N Sense");
GraduateStudent gs("Matt Madox", adv, 1.5); // now add a grade to their grade point average llu.addCourse(3, 2.5); gs.addCourse(3, 3.0); // display the graduate student's qualifier grade cout << "Matt's << gs.qualifier() << endl; // wait until user is ready before terminating program // to allow the user to see the program results system("PAUSE"); return 0; }
This program demonstrates the creation and use of two objects, one of class Student
and a second of GraduateStudent
. The output of this program is as follows:
Constructing student Cy N Sense Constructing student Matt Madox Constructing graduate student Matt Madox Adding grade to Cy N Sense Adding grade to Matt Madox Matt's qualifier grade = 1.5 Press any key to continue...
The class Student
has been defined in the conventional fashion. The class GraduateStudent
is a bit different, however. The colon followed by the phrase public Student
at the beginning of the class definition declares GraduateStudent
to be a subclass of Student
.
The appearance of the keyword public
implies that there is probably protected inheritance as well. All right, it's true, but protected inheritance is beyond the scope of this book.
Programmers love inventing new terms or giving new meaning to existing terms. Heck, programmers even invent new terms and then give them a second meaning. Here is a set of equivalent expressions that describes the same relationship:
GraduateStudent
is a subclass of Student
.
Student
is the base class or is the parent class of GraduateStudent
.
GraduateStudent
inherits or is derived from Student
.
GraduateStudent
extends Student
.
As a subclass of Student, GraduateStudent
inherits all its members. For example, a GraduateStudent
has a name
even though that member is declared up in the base class. However, a subclass can add its own members, for example qualifierGrade
. After all, gs
quite literally IS_A Student
plus a little bit more.
The main()
function declares two objects, llu
of type Student
and gs
of type GraduateStudent
. It then proceeds to access the addCourse()
member function for both types of students. main()
then accesses the qualifier()
function that is only a member of the subclass.
Even though a subclass has access to the protected members of the base class and could initialize them, each subclass is responsible for initializing itself.
Before control passes beyond the open brace of the constructor for GraduateStudent
, control passes to the proper constructor of Student
. If Student
were based on another class, such as Person
, the constructor for that class would be invoked before the Student
constructor got control. Like a skyscraper, the object is constructed starting at the 'base'-ment class and working its way up the class structure one story at a time.
Just as with member objects, you often need to be able to pass arguments to the base class constructor. The example program declares the subclass constructor as follows:
GraduateStudent(const char *pName, Advisor adv, double qG = 0.0) : Student(pName), advisor(adv), qualifierGrade(qG) { // whatever else the constructor does }
Here the constructor for GraduateStudent
invokes the Student
constructor, passing it the argument pName
. C++ then initializes the members advisor
and qualifierGrade
before executing the statements within the constructor's open and close braces.
The default constructor for the base class is executed if the subclass makes no explicit reference to a different constructor. Thus, in the following code snippet, the Pig
base class is constructed before any members of LittlePig
, even though LittlePig
makes no explicit reference to that constructor:
class Pig { public: Pig() : pHouse(nullptr) {} protected: House* pHouse; }; class LittlePig : public Pig { public: LittlePig(double volStraw, int numSticks, int numBricks) : straw(volStraw), sticks(numSticks), bricks(numBricks) { } protected: double straw; int sticks; int bricks; };
Similarly, the copy constructor for a base class is invoked automatically.
Following the rule that destructors are invoked in the reverse order of the constructors, the destructor for GraduateStudent
is given control first. After it's given its last full measure of devotion, control passes to the destructor for Advisor
and then to the destructor for Student
. If Student
were based on a class Person
, the destructor for Person
would get control after Student
.
This is logical. The blob of memory is first converted to a Student
object. Only then is it the job of the GraduateStudent
constructor to transform this simple Student
into a GraduateStudent
. The destructor simply reverses the process.
Notice that the class GraduateStudent
includes the members of class Student
and Advisor
, but in a different way. By defining a data member of class Advisor
, you know that a Student
has all the data members of an Advisor
within it. However, you can't say that a GraduateStudent
is an Advisor
– instead you say that a GraduateStudent
HAS_A Advisor
. What's the difference between this and inheritance?
Use a car as an example. You could logically define a car as being a subclass of vehicle, so it inherits the properties of other vehicles. At the same time, a car has a motor. If you buy a car, you can logically assume that you are buying a motor as well. (Unless you go to the used-car lot where I got my last junk heap.)
If friends ask you to show up at a rally on Saturday with your vehicle of choice and you go in your car, they can't complain (even if someone else shows up on a bicycle) because a car IS_A vehicle. But, if you appear on foot carrying a motor, your friends will have reason to laugh at you because a motor is not a vehicle. A motor is missing certain critical properties that vehicles share – such as radios without AUX plugs for your MP-3 player.
From a programming standpoint, the HAS_A relationship is just as straightforward. Consider the following:
class Vehicle {}; class Motor {}; class Car : public Vehicle { public: Motor motor; }; void VehicleFn(Vehicle& v); void MotorFn(Motor& m); int main(int nNumberofArgs, char* pszArgs[]) { Car car; VehicleFn(car); // this is allowed MotorFn(car); // this is not allowed MotorFn(car.motor);// this is return 0; }
The call VehicleFn(c)
is allowed because car
IS_A vehicle. The call MotorFn(car)
is not because car
is not a Motor
, even though it contains a Motor
. If the intention were to pass the Motor
portion of c
to the function, this must be expressed explicitly, as in the call MotorFn(car.motor)
.
3.144.222.185