Chapter 19. Inheriting a Class

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.

Do I Need My Inheritance?

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.

How Does a Class Inherit?

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...

Using a subclass

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.

Tip

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.

Constructing a 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.

Destructing a subclass

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.

Having a HAS_A Relationship

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).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.226.120