Chapter 28

Inheriting a Class

In This Chapter

arrow Introducing inheritance

arrow Implementing inheritance in C++

arrow Reviewing an example program

arrow Comparing HAS_A to IS_A

Inheritance occurs all around us every day. I am human; I inherit certain properties from the class Human, such as my ability to converse intelligently (more or less) and my dependence on air, water, and carbohydrate-based nourishment like Twinkies. These latter properties are not unique to humans. The class Human inherits these from class Mammal (along with something about bearing live young), which inherits other properties from class Animal, and so on.

The capability to pass down properties is a powerful one. It enables you to describe things in an economical way. For example, if my son asks me, “What’s a duck?” I might say, “It’s a bird that floats and goes quack.” Despite your first reaction, that answer actually conveys a significant amount of knowledge. My son knows what a bird is. He knows that birds have wings, that birds can fly (he doesn’t know about ostriches yet), and that birds lay eggs. Now, he knows all those same things about a duck plus the facts that ducks can float and make a quacking sound. (This might be a good time to refer to Chapter 21 for a discussion about microwave ovens and their relationship to ovens and kitchen appliances.)

Object-oriented languages express this relationship by allowing one class to inherit from another. Thus, in C++, the class Duck might well inherit from Bird, and that class might also inherit from Animal. Exactly how C++ does this is the topic of this chapter.

Advantages of Inheritance

Inheritance was added to C++ for several reasons. Of course, the major reason is the capability to express the inheritance relationship: that MicrowaveOven is an Oven is a KitchenAppliance thing. More on the IS_A relationship a little later in this and the next chapter.

A minor reason is to reduce the amount of typing and the number of lines of code that you and I have to write. You may have noticed that the commands in C++ may be short, but you need a lot of them to do anything. C++ programs tend to get pretty lengthy, so anything that reduces typing is a good thing.

To see how inheritance can reduce typing, consider the Duck example. I don’t have to document all the properties about Duck that have to do with flying and landing and eating and laying eggs. It inherits all that stuff from Bird. I just need to add Duck’s quackness property and its ability to float. That’s a considerable number of keystrokes saved.

A more important and related issue is the major buzzword, reuse. Software scientists realized some time ago that starting from scratch with each new project and rebuilding the same software components doesn’t make much sense.

Compare the situation in the software industry to that in other industries. How many car manufacturers start from scratch each time they want to design a new car? None. Practitioners in other industries have found it makes more sense to start from screws, bolts, nuts, and even larger existing off-the-shelf components such as motors and transmissions when designing a car.

Unfortunately, except for very small functions like those found in the Standard C++ Library, it’s rare to find much reuse of software components. One problem is that it’s virtually impossible to find a component from an earlier program that does exactly what you want. Generally, these components require “tweaking.” Inheritance allows you to adopt the major functionality of an existing class and tweak the smaller features to adapt an existing class to a new application.

This arrangement carries with it another benefit that’s more subtle but just as important: adaptability. It never fails that as soon as users see your most recent program, they like it but want just one more fix or addition. Consider checking accounts for a moment. After I finish the program that handles checking accounts for a bank, how long will it be before the bank comes out with a new “special” checking account that earns interest on the balance?

Not everyone gets this checking account, of course (that would be too easy) — only certain customers get InterestChecking accounts. With inheritance, however, I don’t need to go through the entire program and recode all the checking-account functions. All I need to do is create a new subclass InterestChecking that inherits from Checking but has the one additional property of accumulatesInterest() and, voilà, the feature is implemented. (It isn’t quite that easy, of course, but it’s not much more difficult than that. I show you how to do it in Chapter 29.)

Learning the lingo

You need to get some terms straight before going much further. The class Dog inherits properties from class Mammal. This is called inheritance. We also say that Dog is a subclass of Mammal. Turning that sentence around, we say that Mammal is a base class of Dog. We can also say that Dog IS_A Mammal. (I use all caps as a way of expressing this unique relationship.) C++ shares this terminology with other object-oriented languages.

technicalstuff.eps The term is adopted from other languages, but you will also find C++ programmers saying things like, “the class Dog extends Mammal with its barkiness and tail-wagging properties.” Well, maybe not in those exact words, but a subclass extends a base class by adding properties.

Notice that although Dog IS_A Mammal, the reverse is not true. A Mammal is not a Dog. (A statement like this always refers to the general case. It could be that a particular mammal is, in fact, a dog, but in general a mammal is not a dog.) This is because a Dog shares all the properties of other Mammals, but a Mammal does not have all the properties of a Dog. Not all Mammals can bark, for example, or wag their tails.

Implementing Inheritance in C++

The following is an outline of how to inherit one class from another:

  class Student
{
    // ...whatever goes here...
};

class GraduateStudent : public Student
{
    // ...graduate student unique stuff goes here...
};

The class Student is declared the usual way. The GraduateStudent class appears with the name followed by a colon, the keyword public, and the name of the base class, Student.

technicalstuff.eps The keyword public implies that there’s probably something called protected inheritance. It’s true, there is; but protected inheritance is very uncommon, and I don’t discuss it in this book.

Now, I can say that a GraduateStudent IS_A Student. More to the point, I can use a GraduateStudent object anywhere that a Student is required, including as arguments to functions. That is, the following is allowed:

  void fn(Student* pS);
void someOtherFn()
{
    GraduateStudent gs;
    fn(&gs);
}

This is allowed because a gs object has all the properties of Student. Why? Because a GraduateStudent IS_A Student!

Looking at an example

The following GSInherit program makes this more concrete by creating a Student class and a GraduateStudent class and invoking functions of each:

  //
//  GSInherit - demonstrate inheritance by creating
//              a class GraduateStudent that inherits
//              from Student.
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;

class Student
{
  protected:
    char*  pszName;
    int    nID;
    double dGrade;         // the student's GPA
    int    nSemesterHours;

  public:
    Student(const char* pszNewName, int nNewID)
    {
        cout << "Constructing student "
             << pszNewName << endl;
        pszName = new char[strlen(pszNewName) + 1];
        strcpy(pszName, pszNewName);
        nID = nNewID;
        dGrade = 0.0;
        nSemesterHours = 0;
    }
   ~Student()
    {
        cout << "Destructing " << pszName << endl;
        delete[] pszName;
        pszName = nullptr;
    }

    // access functions
    const char* getName()
    {
        return pszName;
    }
    int getID()
    {
        return nID;
    }
    double getGrade()
    {
        return dGrade;
    }
    int getHours()
    {
        return nSemesterHours;
    }

    // addGrade - add a grade to the GPA and total hours
    double addGrade(double dNewGrade, int nHours)
    {
        double dWtdHrs = dGrade * nSemesterHours;
        dWtdHrs += dNewGrade * nHours;
        nSemesterHours += nHours;
        dGrade = dWtdHrs / nSemesterHours;
        return dGrade;
    }
};

class Advisor
{
  public:
    Advisor() { cout << "Advisor constructed" << endl;}
};

class GraduateStudent : public Student
{
  protected:
    double dQualifierGrade;
    Advisor advisor;

  public:
    GraduateStudent(const char* pszName, int nID) :
       Student(pszName, nID)
    {
        cout << "Constructing GraduateStudent" << endl;
        dQualifierGrade = 0.0;
    }
};

void someOtherFn(Student* pS)
{
    cout << "Passed student " << pS->getName() << endl;
}

void someFn()
{
    Student student("Lo Lee Undergrad", 1234);
    someOtherFn(&student);

    GraduateStudent gs("Upp R. Class", 5678);
    someOtherFn(&gs);
}

int main(int nNumberofArgs, char* pszArgs[])
{
    someFn();

    // wait until user is ready before terminating program
    // to allow the user to see the program results
    cout << "Press Enter to continue..." << endl;
    cin.ignore(10, ' '),
    cin.get();
    return 0;
}

This example appears lengthy at first blush. Fortunately, however, the Student class is identical to its predecessors in earlier chapters.

remember.eps The fact that the Student class hasn’t changed is an important point: You don’t have to modify a class in order to inherit from it. I did not have to make any changes to Student in order to create the subclass GraduateStudent.

The GraduateStudent class extends Student by adding the data member dQualifierGrade. In addition, I provided GraduateStudent with a constructor that accepts the student name and ID. Of course, GraduateStudent doesn’t need to manipulate the student’s name and ID on its own — it calls the perfectly serviceable Student constructor to do that instead, as the following small excerpt demonstrates:

  GraduateStudent(const char* pszName, int nID) :
   Student(pszName, nID)
{
    cout << "Constructing GraduateStudent" << endl;
    dQualifierGrade = 0.0;
}

The constructor for the base class is invoked before any part of the current class is constructed. Next to be invoked are the constructors for any data members — this accounts for the message from Advisor. Control passes into the body of the GraduateStudent constructor last.

The output from this program appears as follows:

  Constructing student Lo Lee Undergrad
Passed student Lo Lee Undergrad
Constructing student Upp R. Class
Advisor constructed
Constructing GraduateStudent
Passed student Upp R. Class
Destructing Upp R. Class
Destructing Lo Lee Undergrad
Press Enter to continue …

You can follow the chain of events by starting with main(). The main() function does nothing more than call someFn(). The someFn() function first creates a Student object Lo Lee Undergrad. The constructor for Student generates the first line of output.

someFn() then passes the address of “Lo Lee” to someOtherFn(Student*). someOtherFn() does nothing more than display the student’s name, which accounts for the second line of output.

The someFn() function then creates a GraduateStudent “Upp R. Class.” Returning to the output for a minute, you can see that this invokes the Student(const char*, int) constructor first with the name Upp R. Class. Once that constructor has completed building the Student foundation, the GraduateStudent constructor gets a chance to output its message and build on the graduate student floor.

The someFn() function then does something rather curious: It passes the address of the GraduateStudent object to someOtherFn(Student*). This apparent mismatch of object types is easily explained by the fact that (here it comes) a GraduateStudent IS_A Student and can be used anywhere a Student is required. (Similarly a GraduateStudent* can be used in place of a Student*.)

The remainder of the output is generated when both student and gs go out of scope at the return from someFn(). The objects are destructed in the reverse order of their construction, so gs goes first and then student. In addition, the destructor for GraduateStudent is called before the destructor for Student().

tip.eps The destructor for the subclass should destruct only those fields that are unique to the subclass. Leave the destructing of the base class data members to the base class’s destructor.

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, a GraduateStudent contains all the members of Advisor within it. However, you can’t say that a GraduateStudent IS_AN Advisor. Rather, a GraduateStudent HAS_AN Advisor.

The analogy is like a car with a motor. Logically, you can say that car is a subclass of vehicle, so it inherits the properties of all 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 some friends ask you to show up at a rally on Saturday with your vehicle of choice, and you arrive in your car, they can’t complain and kick you out. But if you were to appear on foot carrying a motor, your friends would have reason to laugh you off the premises, because a motor is not a vehicle.

These assertions appear as follows when written in C++:

  class Vehicle {};
class Motor {};
class Car : public Vehicle
{
  public:
    Motor motor;
};

void vehicleFn(Vehicle* pV);
void motorFn(Motor* pM);

void someFn()
{
    Car c;

    vehicleFn(&c);     // this is allowed
    motorFn(&c.motor); // so is this

    motorFn(&c);       // this is not allowed
}

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

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