Chapter 15 Inheritance, Polymorphism, and Virtual Functions

15.1 What Is Inheritance?

Concept:
  • Inheritance enables a new class, known as the derived class, to be created based on an existing class, the base class.
  • The new class inherits all member variables and functions from the base class, with the exception of constructors and the destructor.
  • In C++11 and later, a class can optionally inherit some of its base class’s constructors.

Generalization and Specialization

  • Many real-world objects can be viewed as specialized versions of more general ones.
  • For instance, “insect” is a general category. Grasshoppers and bumblebees are specialized types of insects.
  • They share the general characteristics of insects but also possess unique traits, such as a grasshopper’s jumping ability or a bumblebee’s stinger.

Inheritance and the “Is a” Relationship

  • The relationship between a specialized object and a more general one is called an “is a” relationship. For example, a poodle is a dog.

  • In object-oriented programming, inheritance is the mechanism used to establish an “is a” relationship between classes.

  • This involves a base class (the general class, or parent) and a derived class (the specialized class, or child).

  • The derived class inherits members from the base class and can also have its own unique members added to it.

  • As an example, consider a GradedActivity class that holds a numeric score and determines a letter grade.

Contents of GradedActivity.h (Version 1)
 #ifndef GRADEDACTIVITY_H
 #define GRADEDACTIVITY_H


 class GradedActivity
 {
 private:
    double score; 
 public:
    GradedActivity()
       { score = 0.0; }

    GradedActivity(double s)
       { score = s; }

    void setScore(double s)
       { score = s; }

    double getScore() const
       { return score; }

    char getLetterGrade() const;
 };
 #endif
Contents of GradedActivity.cpp (Version 1)
 #include "GradedActivity.h"


 char GradedActivity::getLetterGrade() const
 {
    char letterGrade; 

    if (score > 89)
       letterGrade = 'A';
    else if (score > 79)
       letterGrade = 'B';
    else if (score > 69)
       letterGrade = 'C';
    else if (score > 59)
       letterGrade = 'D';
    else
       letterGrade = 'F';

    return letterGrade;
 }
  • The GradedActivity class includes constructors to initialize the score, a setScore mutator, and a getLetterGrade accessor.

🗊 Program 15-1

 #include <iostream>
 #include "GradedActivity.h"
 using namespace std;

 int main()
 {
    double testScore;  

    GradedActivity test;

    cout << "Enter your numeric test score: ";
    cin >> testScore;

    test.setScore(testScore);

    cout << "The grade for that test is "
         << test.getLetterGrade() << endl;

    return 0;
 }

💻 Program Output



💻 Program Output



  • To handle specific types of graded activities, such as a final exam, we can create a derived class.
  • The FinalExam class, for example, is derived from GradedActivity and adds members to track the number of questions, points per question, and questions missed.
Contents of FinalExam.h
 #ifndef FINALEXAM_H
 #define FINALEXAM_H
 #include "GradedActivity.h"

 class FinalExam : public GradedActivity
 {
 private:
    int numQuestions;  
    double pointsEach; 
    int numMissed;     
 public:
    FinalExam()
       { numQuestions = 0;
         pointsEach = 0.0;
         numMissed = 0; }

    FinalExam(int questions, int missed)
       { set(questions, missed); }

    void set(int, int);  

    double getNumQuestions() const
       { return numQuestions; }

    double getPointsEach() const
       { return pointsEach; }

    int getNumMissed() const
       { return numMissed; }
 };
 #endif
Contents of FinalExam.cpp
 #include "FinalExam.h"


 void FinalExam::set(int questions, int missed)
 {
    double numericScore;  

    numQuestions = questions;
    numMissed = missed;

    pointsEach = 100.0 / numQuestions;

    numericScore = 100.0 - (missed * pointsEach);

    setScore(numericScore);
 }
  • The declaration class FinalExam : public GradedActivity indicates that FinalExam is the derived class and GradedActivity is the base class. This establishes a “FinalExam is a GradedActivity” relationship.
  • The public keyword is the base class access specification. It determines how the base class members are inherited.
  • With public access specification, public members of GradedActivity become public members of FinalExam.
  • private members of GradedActivity (like score) are inherited but are inaccessible directly by FinalExam’s member functions. They can only be accessed via the public member functions of the base class.
  • Constructors are not inherited.
  • The FinalExam::set function calculates the numeric score and then calls the inherited setScore function to store it.
Private Members:
int numQuestions Declared in the FinalExamclass
double pointsEach Declared in the FinalExamclass
int numMissed Declared in the FinalExamclass
Public Members:
FinalExam() Defined in the FinalExamclass
FinalExam(int, int) Defined in the FinalExamclass
set(int, int) Defined in the FinalExamclass
getNumQuestions() Defined in the FinalExamclass
getPointsEach() Defined in the FinalExamclass
getNumMissed() Defined in the FinalExamclass
setScore(double) Inherited from GradedActivity
getScore() Inherited from GradedActivity
getLetterGrade() Inherited from GradedActivity

🗊 Program 15-2

 #include <iostream>
 #include <iomanip>
 #include "FinalExam.h"
 using namespace std;

 int main()
 {
    int questions; 
    int missed;    

    cout << "How many questions are on the final exam? ";
    cin >> questions;

    cout << "How many questions did the student miss? ";
    cin >> missed;

    FinalExam test(questions, missed);

    cout << setprecision(2);
    cout << "\nEach question counts " << test.getPointsEach()
         << " points.\n";
    cout << "The exam score is " << test.getScore() << endl;
    cout << "The exam grade is " << test.getLetterGrade() << endl;

    return 0;
 }

💻 Program Output






  • A FinalExam object can directly call public member functions inherited from GradedActivity, such as getScore() and getLetterGrade().
  • Inheritance is a one-way relationship; a base class cannot call member functions of a derived class.
class BadBase
{
   private:
      int x;
   public:
      BadBase() { x = getVal(); }  
};
class Derived : public BadBase
{
   private:
      int y;
   public:
      Derived(int z) { y = z; }
      int getVal() { return y; }
};

Checkpoint

  1. 15.1 Here is the first line of a class declaration. What is the name of the base class?

    class Truck : public Vehicle
  2. 15.2 What is the name of the derived class in the following declaration line?

    class Truck : public Vehicle

  3. 15.3 Suppose a program has the following class declarations:

    class Shape
    {
    private:
       double area;
    public:
       void setArea(double a)
          { area = a; }
       double getArea()
          { return area; }
    };
    class Circle : public Shape
    {
    private:
       double radius;
    public:
       void setRadius(double r)
          { radius = r;
            setArea(3.14 * r * r); }
       double getRadius()
          { return radius; }
    };

    Answer the following questions concerning these classes:

    1. When an object of the Circle class is created, what are its private members?

    2. When an object of the Circle class is created, what are its public members?

    3. What members of the Shape class are not accessible to member functions of the Circle class?


15.2 Protected Members and Class Access

Concept:
  • Protected members of a base class function like private members but can be accessed by derived classes.
  • The base class access specification controls how private, public, and protected base class members are accessed by the derived class.
  • C++ offers a third access specifier, protected.

  • protected members of a base class can be accessed by member functions of that base class and by member functions of any derived classes.

  • To code outside the base or derived classes, protected members are inaccessible, just like private members.

  • Here is a modified GradedActivity class where the score member is changed from private to protected.

Contents of GradedActivity.h (Version 2)
 #ifndef GRADEDACTIVITY_H
 #define GRADEDACTIVITY_H


 class GradedActivity
 {
 protected:
    double score;  
 public:
    GradedActivity()
       { score = 0.0; }

    GradedActivity(double s)
       { score = s; }

    void setScore(double s)
       { score = s; }

    double getScore() const
       { return score; }

    char getLetterGrade() const;
 };
 #endif
  • Because score is now protected, a derived class like FinalExam can directly access and modify it.
  • A new function, adjustScore, is added to FinalExam to round the score up if its fractional part is 0.5 or greater. This function directly accesses the inherited score member.
Contents of FinalExam.h (Version 2)
 #ifndef FINALEXAM_H
 #define FINALEXAM_H
 #include "GradedActivity.h"

 class FinalExam : public GradedActivity
 {
 private:
    int numQuestions;  
    double pointsEach; 
    int numMissed;     
 public:
    FinalExam()
       { numQuestions = 0;
         pointsEach = 0.0;
         numMissed = 0; }

    FinalExam(int questions, int missed)
       { set(questions, missed); }

    void set(int, int); 
    void adjustScore(); 

    double getNumQuestions() const
       { return numQuestions; }

    double getPointsEach() const
       { return pointsEach; }

    int getNumMissed() const
       { return numMissed; }
 };
 #endif
Contents of FinalExam.cpp (Version 2)
 #include "FinalExam.h"


 void FinalExam::set(int questions, int missed)
 {
    double numericScore;  

    numQuestions = questions;
    numMissed = missed;

    pointsEach = 100.0 / numQuestions;

    numericScore = 100.0 - (missed * pointsEach);

    setScore(numericScore);

    adjustScore();
 }


 void FinalExam::adjustScore()
 {
    double fraction = score - static_cast<int>(score);

    if (fraction >= 0.5)
    {
       score += (1.0 - fraction);
    }
 }

🗊 Program 15-3

 #include <iostream>
 #include <iomanip>
 #include "FinalExam.h"
 using namespace std;

 int main()
 {
    int questions; 
    int missed;    

    cout << "How many questions are on the final exam? ";
    cin >> questions;

    cout << "How many questions did the student miss? ";
    cin >> missed;

    FinalExam test(questions, missed);

    cout << setprecision(2) << fixed;
    cout << "\nEach question counts "
         << test.getPointsEach() << " points.\n";
    cout << "The adjusted exam score is "
         << test.getScore() << endl;
    cout << "The exam grade is "
         << test.getLetterGrade() << endl;

    return 0;
 }

💻 Program Output






More about Base Class Access Specification

  • Base class access specification controls how inherited members are accessed in the derived class. This is distinct from member access specification (private, protected, public), which applies to members defined within a class.
  • The base class access specification acts as a filter for inherited members.
Table 15-1 Base Class Access Specification
Base Class Access Specification How Members of the Base Class Appear in the Derived Class
private

Private members of the base class are inaccessible to the derived class.

Protected members of the base class become private members of the derived class.

Public members of the base class become private members of the derived class.

protected

Private members of the base class are inaccessible to the derived class.

Protected members of the base class become protected members of the derived class.

Public members of the base class become protected members of the derived class.

public

Private members of the base class are inaccessible to the derived class.

Protected members of the base class become protected members of the derived class.

Public members of the base class become public members of the derived class.

Note:
  • If the base class access specification is omitted, it defaults to private.
class Test : Grade 

Checkpoint

  1. 15.4 What is the difference between private members and protected members?

  2. 15.5 What is the difference between member access specification and class access specification?

  3. 15.6 Suppose a program has the following class declaration:

     class CheckPoint
    {
        private:
           int a;
        protected:
           int b;
           int c;
           void setA(int x) { a = x;}
        public:
           void setB(int y) { b = y;}
           void setC(int z) { c = z;}
     };

    Answer the following questions regarding the class:

    1. Suppose another class, Quiz, is derived from the CheckPoint class. Here is the first line of its declaration:

      class Quiz : private CheckPoint

      Indicate whether each member of the CheckPoint class is private, protected, public, or inaccessible:

      a
      b
      c
      setA
      setB
      setC
    2. Suppose the Quiz class, derived from the CheckPoint class, is declared as

      class Quiz : protected Checkpoint

      Indicate whether each member of the CheckPoint class is private, protected, public, or inaccessible:

      a
      b
      c
      setA
      setB
      setC
    3. Suppose the Quiz class, derived from the CheckPoint class, is declared as

      class Quiz : public Checkpoint

      Indicate whether each member of the CheckPoint class is private, protected, public, or inaccessible:

      a
      b
      c
      setA
      setB
      setC
    4. Suppose the Quiz class, derived from the CheckPoint class, is declared as

      class Quiz : Checkpoint

      Is the CheckPoint class a private, public, or protected base class?

15.3 Constructors and Destructors in Base and Derived Classes

Concept:
  • When an object of a derived class is created, the base class’s constructor is executed first, followed by the derived class’s constructor.
  • Destructors are called in the reverse order: the derived class’s destructor is called first, then the base class’s destructor.
  • In an inheritance relationship, constructors are executed from the base class down to the derived class.
  • Destructors are executed in the opposite order, from the derived class up to the base class.
  • The following program demonstrates this sequence by displaying messages from the constructors and destructors of a base and derived class.

🗊 Program 15-4

 #include <iostream>
 using namespace std;


 class BaseClass
 {
 public:
    BaseClass()   
       { cout << "This is the BaseClass constructor.\n"; }

    ~BaseClass()  
       { cout << "This is the BaseClass destructor.\n"; }
 };


 class DerivedClass : public BaseClass
 {
 public:
    DerivedClass()   
       { cout << "This is the DerivedClass constructor.\n"; }

    ~DerivedClass()  
       { cout << "This is the DerivedClass destructor.\n"; }
 };


 int main()
 {
    cout << "We will now define a DerivedClass object.\n";

    DerivedClass object;

    cout << "The program is now going to end.\n";
    return 0;
 }

💻 Program Output







Passing Arguments to Base Class Constructors

  • If a base class constructor requires arguments, the derived class constructor is responsible for passing those arguments to it.
  • Consider a Rectangle class with multiple constructors.
Contents of Rectangle.h
 #ifndef RECTANGLE_H
 #define RECTANGLE_H

 class Rectangle
 {
 private:
    double width;
    double length;
 public:
    Rectangle()
       { width = 0.0;
         length = 0.0; }

    Rectangle(double w, double len)
       { width = w;
         length = len; }

    double getWidth() const
       { return width; }

    double getLength() const
       { return length; }

    double getArea() const
       { return width * length; }
 };
 #endif
  • A Box class derived from Rectangle must call the appropriate Rectangle constructor.
Contents of Box.h
 #ifndef BOX_H
 #define BOX_H
 #include "Rectangle.h"

 class Box : public Rectangle
 {
 protected:
    double height;
    double volume;
 public:
    Box() : Rectangle()
    { height = 0.0; volume = 0.0; }

     Box(double w, double len, double h) : Rectangle(w, len)
    { height = h;
      volume = getArea() * h; }

    double getHeight() const
    { return height; }

    double getVolume() const
    { return volume; }
 };
 #endif
  • A special syntax is used in the derived class constructor’s header to call the base class constructor.

  • The syntax involves placing a colon after the constructor’s parameter list, followed by a call to the base class constructor. For example: Box() : Rectangle() calls the base class’s default constructor.

  • The general format is: ClassName::ClassName(ParameterList) : BaseClassName(ArgumentList)

  • Arguments can also be passed, as seen in the second Box constructor: Box(double w, double len, double h) : Rectangle(w, len). Here, w and len are passed up to the Rectangle constructor.

  • This notation is used in the constructor’s definition, not its prototype.

  • The base class constructor always executes before the derived class constructor’s body.

  • Arguments passed to the base class constructor can be parameters from the derived class constructor, literal values, or accessible global variables.

🗊 Program 15-5

 #include <iostream>
 #include "Box.h"
 using namespace std;

 int main()
 {
    double boxWidth;  
    double boxLength; 
    double boxHeight; 

    cout << "Enter the dimensions of a box:\n";
    cout << "Width: ";
    cin >> boxWidth;
    cout << "Length: ";
    cin >> boxLength;
    cout << "Height: ";
    cin >> boxHeight;

    Box myBox(boxWidth, boxLength, boxHeight);

    cout << "Here are the box's properties:\n";
    cout << "Width: " << myBox.getWidth() << endl;
    cout << "Length: " << myBox.getLength() << endl;
    cout << "Height: " << myBox.getHeight() << endl;
    cout << "Base area: " << myBox.getArea() << endl;
    cout << "Volume: " << myBox.getVolume() << endl;
    return 0;
 }

💻 Program Output











Note:
  • If a base class does not have a default constructor, any derived class must explicitly call one of the base class’s other constructors.
In the Spotlight:

The Automobile, Car, Truck, and SUV Classes

  • To manage a car dealership’s inventory, it’s inefficient to create separate, unrelated classes for cars, trucks, and SUVs because they share many common attributes (make, model, mileage, price).
  • A better design uses an Automobile base class for the common data and derived classes (Car, Truck, SUV) for specific data.
Contents of Automobile.h
 #ifndef AUTOMOBILE_H
 #define AUTOMOBILE_H
 #include <string>
 using namespace std;

 class Automobile
 {
 private:
    string make;  
    int model;    
    int mileage;  
    double price; 

 public:
    Automobile()
    {  make = "";
       model = 0;
       mileage = 0;
       price = 0.0; }

    Automobile(string autoMake, int autoModel,
              int autoMileage, double autoPrice)
    {  make = autoMake;
       model = autoModel;
       mileage = autoMileage;
       price = autoPrice; }

    string getMake() const
    { return make; }

    int getModel() const
    { return model; }

    int getMileage() const
    { return mileage; }

    double getPrice() const
    { return price; }
 };
 #endif
  • The Car class inherits from Automobile and adds a doors attribute. Its constructors call the corresponding Automobile constructors to initialize the inherited members.
Contents of Car.h
 #ifndef CAR_H
 #define CAR_H
 #include "Automobile.h"
 #include <string>
 using namespace std;

 class Car : public Automobile
 {
 private:
    int doors;

 public:
    Car() : Automobile()
    { doors = 0;}

    Car(string carMake, int carModel, int carMileage,
       double carPrice, int carDoors) :
       Automobile(carMake, carModel, carMileage, carPrice)
    { doors = carDoors; }

    int getDoors()
    {return doors;}
 };
 #endif
  • Similarly, the Truck class inherits from Automobile and adds a driveType attribute, with constructors that call the base class constructors.
Contents of Truck.h
 #ifndef TRUCK_H
 #define TRUCK_H
 #include "Automobile.h"
 #include <string>
 using namespace std;

 class Truck : public Automobile
 {
 private:
    string driveType;

 public:
    Truck() : Automobile()
    { driveType = ""; }

    Truck(string truckMake, int truckModel, int truckMileage,
       double truckPrice, string truckDriveType) :
        Automobile(truckMake, truckModel, truckMileage, truckPrice)
    { driveType = truckDriveType; }

    string getDriveType()
    { return driveType; }
 };
 #endif
  • The SUV class also inherits from Automobile and adds a passengers attribute, following the same constructor pattern.
Contents of SUV.h
 #ifndef SUV_H
 #define SUV_H
 #include "Automobile.h"
 #include <string>
 using namespace std;

 class SUV : public Automobile
 {
 private:
     int passengers;

 public:
     SUV() : Automobile()
     { passengers = 0; }

     SUV(string SUVMake, int SUVModel, int SUVMileage,
         double SUVPrice, int SUVPassengers) :
         Automobile(SUVMake, SUVModel, SUVMileage, SUVPrice)
     { passengers = SUVPassengers; }

     int getPassengers()
     { return passengers; }
 };
 #endif
  • The following program demonstrates the creation and use of objects from each of these derived classes.

🗊 Program 15-6

 #include <iostream>
 #include <iomanip>
 #include "Car.h"
 #include "Truck.h"
 #include "SUV.h"
 using namespace std;

 int main()
 {
     Car car("BMW", 2007, 50000, 15000.0, 4);

     Truck truck("Toyota", 2006, 40000, 12000.0, "4WD");

     SUV suv("Volvo", 2005, 30000, 18000.0, 5);

     cout << fixed << showpoint << setprecision(2);
     cout << "We have the following car in inventory:\n"
          << car.getModel() << " " << car.getMake()
          << " with " << car.getDoors() << " doors and "
          << car.getMileage() << " miles.\nPrice: $"
          << car.getPrice() << endl << endl;

     cout << "We have the following truck in inventory:\n"
          << truck.getModel() << " " << truck.getMake()
          << " with " << truck.getDriveType()
          << " drive type and " << truck.getMileage()
          << " miles.\nPrice: $" << truck.getPrice()
          << endl << endl;

     cout << "We have the following SUV in inventory:\n"
          << suv.getModel() << " " << suv.getMake()
          << " with " << suv.getMileage() << " miles and "
          << suv.getPassengers() << " passenger capacity.\n"
          << "Price: $" << suv.getPrice() << endl;

     return 0;
 }

💻 Program Output










Constructor Inheritance

  • C++ 11 allows a derived class to inherit constructors from its base class using a using declaration.
  • The default constructor, copy constructor, and move constructor cannot be inherited this way.
  • This is useful when a derived class constructor’s only job is to call a base class constructor.
  • The syntax is using BaseClassName::BaseClassName; inside the derived class definition.
  • This allows instances of the derived class to be created by calling the inherited base class constructors directly.
  • A derived class can still have its own constructors in addition to the inherited ones.
  • If a derived class defines a constructor with the same parameter list as a base class constructor, the base class version is not inherited.

Checkpoint

  1. 15.7 What will the following program display?

    #include <iostream>
    using namespace std;
    class Sky
    {
    public:
       Sky()
          { cout << "Entering the sky.\n"; }
       ~Sky()
          { cout << "Leaving the sky.\n"; }
    };
    class Ground : public Sky
    {
    public:
       Ground()
          { cout << "Entering the Ground.\n"; }
       ~Ground()
          { cout << "Leaving the Ground.\n"; }
    };
    int main()
    {
       Ground object;
       return 0;
    }
  2. 15.8 What will the following program display?

    #include <iostream>
    using namespace std;
    
    class Sky
    {
    public:
       Sky()
          { cout << "Entering the sky.\n"; }
       Sky(string color)
          { cout << "The sky is " << color << endl; }
       ~Sky()
          { cout << "Leaving the sky.\n"; }
    };
    class Ground : public Sky
    {
    public:
       Ground()
          { cout << "Entering the Ground.\n"; }
       Ground(string c1, string c2) : Sky(c1)
          { cout << "The ground is " << c2 << endl; }
       ~Ground()
          { cout << "Leaving the Ground.\n"; }
    };
    int main()
    {
       Ground object;
       return 0;
    }

15.4 Redefining Base Class Functions

Concept:
  • A derived class can redefine a member function from its base class.

Redefining a Base Class Function in a Derived Class

  • Function redefinition is useful for extending or modifying the behavior of a base class.
  • For instance, a GradedActivity class might determine a letter grade from a numeric score.
  • A derived class, CurvedActivity, could redefine the function that sets the score to first apply a curve.
Contents of CurvedActivity.h
 #ifndef CURVEDACTIVITY_H
 #define CURVEDACTIVITY_H
 #include "GradedActivity.h"

 class CurvedActivity : public GradedActivity
 {
 protected:
     double rawScore;    
     double percentage;  
 public:
     CurvedActivity() : GradedActivity()
        { rawScore = 0.0; percentage = 0.0; }

     void setScore(double s)
        { rawScore = s;
          GradedActivity::setScore(rawScore * percentage); }

     void setPercentage(double c)
        { percentage = c; }

     double getPercentage() const
        { return percentage; }

     double getRawScore() const
        { return rawScore; }
 };
 #endif
  • When a derived class member function has the same name as a base class function, it is said to redefine the base function.
  • Objects of the derived class will call the derived class’s version of the function.
  • Redefining is different from overloading. Overloading involves functions with the same name but different parameter lists, all within the same scope. Redefining occurs across a base and derived class.
  • A redefined function in a derived class can explicitly call the base class version using the scope resolution operator: BaseClassName::functionName(ArgumentList);.
  • In the CurvedActivity example, setScore calls GradedActivity::setScore() to pass the adjusted score to the base class.
Note:
  • The CurvedActivity class has a protected member section, which is a good practice in case it is later used as a base class for another class.

🗊 Program 15-7

 #include <iostream>
 #include <iomanip>
 #include "CurvedActivity.h"
 using namespace std;

 int main()
 {
     double numericScore;  
     double percentage;    

     CurvedActivity exam;

     cout << "Enter the student's raw numeric score: ";
     cin >> numericScore;

     cout << "Enter the curve percentage for this student: ";
     cin >> percentage;

     exam.setPercentage(percentage);
     exam.setScore(numericScore);

     cout << fixed << setprecision(2);
     cout << "The raw score is "
          << exam.getRawScore() << endl;
     cout << "The curved score is "
          << exam.getScore() << endl;
     cout << "The curved grade is "
          << exam.getLetterGrade() << endl;

     return 0;
 }

💻 Program Output






  • It’s important to remember that objects of the base class type will always call the base class version of a function, even if it is redefined in a derived class.

🗊 Program 15-8

 #include <iostream>
 using namespace std;

 class BaseClass
 {
 public:
     void showMessage()
        { cout << "This is the Base class.\n";}
 };

 class DerivedClass : public BaseClass
 {
 public:
     void showMessage()
        { cout << "This is the Derived class.\n"; }
 };

 int main()
 {
     BaseClass b;
     DerivedClass d;

     b.showMessage();
     d.showMessage();

     return 0;
 }

💻 Program Output




15.5 Class Hierarchies

Concept:
  • A class can be derived from another class, which itself is derived from another class, forming a class hierarchy.
  • This chain of inheritance can extend for many levels, with each class inheriting the members of all its ancestors.

  • As an example, a PassFailActivity class can be derived from GradedActivity to handle pass/fail grading.

Contents of PassFailActivity.h
 #ifndef PASSFAILACTIVITY_H
 #define PASSFAILACTIVITY_H
 #include "GradedActivity.h"

 class PassFailActivity : public GradedActivity
 {
 protected:
     double minPassingScore;  
 public:
     PassFailActivity() : GradedActivity()
        { minPassingScore = 0.0; }

     PassFailActivity(double mps) : GradedActivity()
         { minPassingScore = mps; }

     void setMinPassingScore(double mps)
         { minPassingScore = mps; }

     double getMinPassingScore() const
         { return minPassingScore; }

     char getLetterGrade() const;
 };
 #endif
  • This class redefines the getLetterGrade function to return ‘P’ (Pass) or ‘F’ (Fail) based on a minimum passing score.
Contents of PassFailActivity.cpp
 #include "PassFailActivity.h"


 char PassFailActivity::getLetterGrade() const
 {
     char letterGrade;

     if (score >= minPassingScore)
         letterGrade = 'P';
     else
         letterGrade = 'F';

     return letterGrade;
 }
  • A more specialized class, such as PassFailExam, can then be derived from PassFailActivity.
  • PassFailExam inherits all members from PassFailActivity, including those PassFailActivity inherited from GradedActivity.
Contents of PassFailExam.h
 #ifndef PASSFAILEXAM_H
 #define PASSFAILEXAM_H
 #include "PassFailActivity.h"

 class PassFailExam : public PassFailActivity
 {
 private:
     int numQuestions;    
     double pointsEach;   
     int numMissed;       
 public:
     PassFailExam() : PassFailActivity()
         { numQuestions = 0;
           pointsEach = 0.0;
           numMissed = 0; }

     PassFailExam(int questions, int missed, double mps) :
         PassFailActivity(mps)
         { set(questions, missed); }

     void set(int, int);  

     double getNumQuestions() const
         { return numQuestions; }

     double getPointsEach() const
         { return pointsEach; }

     int getNumMissed() const
         { return numMissed; }
 };
 #endif
Contents of PassFailExam.cpp
 #include "PassFailExam.h"


 void PassFailExam::set(int questions, int missed)
 {
     double numericScore; 

     numQuestions = questions;
     numMissed = missed;

     pointsEach = 100.0 / numQuestions;

     numericScore = 100.0 - (missed * pointsEach);

     setScore(numericScore);
 }
  • The PassFailExam class contains its own members plus all the public and protected members from its entire inheritance chain.
Table 15-2 Member Variables of the PassFailExam Class
Member Variable Access Inherited?
numQuestions protected No
pointsEach protected No
numMissed protected No
minPassingScore protected Yes, from PassFailActivity
score protected Yes, from PassFailActivity, which inherited it from GradedActivity
Table 15-3 Member Functions of the PassFailExam Class
Member Function Access Inherited?
set public No
getNumQuestions public No
getPointsEach public No
getNumMissed public No
setMinPassingScore public Yes, from PassFailActivity
getMinPassingScore public Yes, from PassFailActivity
getLetterGrade public Yes, from PassFailActivity
setScore public Yes, from PassFailActivity, which inherited it from GradedActivity
getScore public Yes, from PassFailActivity, which inherited it from GradedActivity

🗊 Program 15-9

 #include <iostream>
 #include <iomanip>
 #include "PassFailExam.h"
 using namespace std;

 int main()
 {
     int questions;        
     int missed;           
     double minPassing;    

     cout << "How many questions are on the exam? ";
     cin >> questions;

     cout << "How many questions did the student miss? ";
     cin >> missed;

     cout << "Enter the minimum passing score for this test: ";
     cin >> minPassing;

     PassFailExam exam(questions, missed, minPassing);

     cout << fixed << setprecision(1);
     cout << "\nEach question counts "
          << exam.getPointsEach() << " points.\n";
     cout << "The minimum passing score is "
          << exam.getMinPassingScore() << endl;
     cout << "The student's exam score is "
          << exam.getScore() << endl;
     cout << "The student's grade is "
          << exam.getLetterGrade() << endl;
     return 0;
 }

💻 Program Output








  • Because PassFailExam is derived from PassFailActivity, it inherits the redefined getLetterGrade function that reports ‘P’ or ‘F’.
  • Class hierarchy diagrams are used to visualize inheritance relationships, with general classes at the top and specialized classes at the bottom.

15.6 Polymorphism and Virtual Member Functions

Concept:
  • Polymorphism is a feature that allows a base class pointer or reference to refer to objects of different derived types.
  • It enables the program to call the correct member function for the object’s actual type.

Polymorphism

  • Consider a function displayGrade that takes a GradedActivity reference as a parameter.
void displayGrade(const GradedActivity &activity)
{
   cout << setprecision(1) << fixed;
   cout << "The activity's numeric score is "
        << activity.getScore() << endl;
   cout << "The activity's letter grade is "
        << activity.getLetterGrade() << endl;
}
  • Because of the “is-a” relationship, you can pass an object of a class derived from GradedActivity (like FinalExam) to this function.
  • A problem occurs when a derived class redefines a member function. For example, the PassFailActivity class redefines getLetterGrade.
  • When a PassFailActivity object is passed to displayGrade, the base class’s getLetterGrade function is called, not the redefined one, leading to incorrect output.

🗊 Program 15-10

 #include <iostream>
 #include <iomanip>
 #include "PassFailActivity.h"
 using namespace std;

 void displayGrade(const GradedActivity &);

 int main()
 {
     PassFailActivity test(70);

     test.setScore(72);

     displayGrade(test);
     return 0;
 }


 void displayGrade(const GradedActivity &activity)
 {
     cout << setprecision(1) << fixed;
     cout << "The activity's numeric score is "
          << activity.getScore() << endl;
     cout << "The activity's letter grade is "
          << activity.getLetterGrade() << endl;
 }

💻 Program Output



  • This issue happens because of static binding, where the compiler determines at compile time which function to call based on the parameter’s type (GradedActivity), not the actual object’s type (PassFailActivity).
  • To fix this, the function can be made virtual. A virtual function uses dynamic binding, where the decision of which function to call is made at runtime, based on the object’s actual type.
  • To declare a virtual function, use the virtual keyword in the base class function declaration.
virtual char getLetterGrade() const;
Note:
  • The virtual keyword is used only in the function’s declaration or prototype within the class, not in the external definition.
  • Here is the updated GradedActivity class with a virtual function.
Contents ofGradedActivity.h (Version 3)
 #ifndef GRADEDACTIVITY_H
 #define GRADEDACTIVITY_H


 class GradedActivity
 {
 protected:
     double score;  
 public:
     GradedActivity()
        { score = 0.0; }

     GradedActivity(double s)
        { score = s; }

     void setScore(double s)
        { score = s; }

     double getScore() const
        { return score; }

     virtual char getLetterGrade() const;
 };
 #endif
  • When a base class function is virtual, any redefined versions in derived classes automatically become virtual. It is good practice to also declare them as virtual for clarity.
Contents of PassFailActivity.h
 #ifndef PASSFAILACTIVITY_H
 #define PASSFAILACTIVITY_H
 #include "GradedActivity.h"

 class PassFailActivity : public GradedActivity
 {
 protected:
     double minPassingScore;  
  public:
     PassFailActivity() : GradedActivity()
        { minPassingScore = 0.0; }

     PassFailActivity(double mps) : GradedActivity()
        { minPassingScore = mps; }

     void setMinPassingScore(double mps)
        { minPassingScore = mps; }

     double getMinPassingScore() const
        { return minPassingScore; }

     virtual char getLetterGrade() const;
 };
 #endif
  • With the getLetterGrade function now virtual, the program works as expected.

🗊 Program 15-11

 #include <iostream>
 #include <iomanip>
 #include "PassFailActivity.h"
 using namespace std;

 void displayGrade(const GradedActivity &);

 int main()
 {
     PassFailActivity test(70);

     test.setScore(72);

     displayGrade(test);
     return 0;
 }


 void displayGrade(const GradedActivity &activity)
 {
     cout << setprecision(1) << fixed;
     cout << "The activity's numeric score is "
          << activity.getScore() << endl;
     cout << "The activity's letter grade is "
          << activity.getLetterGrade() << endl;
 }

💻 Program Output



  • Polymorphism, meaning “the ability to take many forms,” allows the displayGrade function to work correctly with objects of different classes derived from GradedActivity.

🗊 Program 15-12

 #include <iostream>
 #include <iomanip>
 #include "PassFailExam.h"
 using namespace std;

 void displayGrade(const GradedActivity &);

 int main()
 {
     GradedActivity test1(88.0);

     PassFailExam test2(100, 25, 70.0);

     cout << "Test 1:\n";
     displayGrade(test1);    
     cout << "\nTest 2:\n";
     displayGrade(test2);    
     return 0;
 }


 void displayGrade(const GradedActivity &activity)
 {
     cout << setprecision(1) << fixed;
     cout << "The activity's numeric score is "
          << activity.getScore() << endl;
     cout << "The activity's letter grade is "
          << activity.getLetterGrade() << endl;
 }

💻 Program Output







Polymorphism Requires References or Pointers

  • Polymorphic behavior is only achieved when using base class references or pointers.
  • If an object is passed by value, static binding occurs, and the base class’s version of the function is always called.
  • Using a base class pointer as a parameter also enables polymorphism.

🗊 Program 15-13

 #include <iostream>
 #include <iomanip>
 #include "PassFailExam.h"
 using namespace std;

 void displayGrade(const GradedActivity *);

 int main()
 {
     GradedActivity test1(88.0);

     PassFailExam test2(100, 25, 70.0);

     cout << "Test 1:\n";
     displayGrade(&test1);  
     cout << "\nTest 2:\n";
     displayGrade(&test2);  
     return 0;
 }


 void displayGrade(const GradedActivity *activity)
 {
     cout << setprecision(1) << fixed;
     cout << "The activity's numeric score is "
          << activity->getScore() << endl;
     cout << "The activity's letter grade is "
          << activity->getLetterGrade() << endl;
 }

💻 Program Output







Base Class Pointers

  • A base class pointer can be assigned the address of a derived class object.
  • This allows for creating heterogeneous collections, such as an array of base class pointers where each element points to an object of a different derived class.

🗊 Program 15-14

 #include <iostream>
 #include <iomanip>
 #include "PassFailExam.h"
 using namespace std;

 void displayGrade(const GradedActivity *);

 int main()
 {
     const int NUM_TESTS = 4;

     GradedActivity *tests[NUM_TESTS] =
         { new GradedActivity(88.0),
           new PassFailExam(100, 25, 70.0),
           new GradedActivity(67.0),
           new PassFailExam(50, 12, 60.0)
         };

     for (int count = 0; count < NUM_TESTS; count++)
     {
         cout << "Test #" << (count + 1) << ":\n";
         displayGrade(tests[count]);
         cout << endl;
     }
     return 0;
 }


 void displayGrade(const GradedActivity *activity)
 {
     cout << setprecision(1) << fixed;
     cout << "The activity's numeric score is "
          << activity->getScore() << endl;
     cout << "The activity's letter grade is "
          << activity->getLetterGrade() << endl;
 }

💻 Program Output













Base Class Pointers and References Know Only about Base Class Members

  • A base class pointer or reference can only be used to call members that are defined in the base class.
  • Attempting to call a derived-class-specific member through a base class pointer will result in a compiler error.

The “Is-a” Relationship Does Not Work in Reverse

  • The “is-a” relationship is one-way: a FinalExam is a GradedActivity, but a GradedActivity is not necessarily a FinalExam.
  • You cannot assign the address of a base class object to a derived class pointer without an explicit type cast.
  • Even with a type cast, calling derived-class-specific members on a base class object will cause a runtime error.

Redefining versus Overriding

  • Redefining occurs when a derived class function has the same name as a base class function. These calls are statically bound.
  • Overriding occurs when a derived class provides its own version of a virtual function from the base class. These calls are dynamically bound.

Virtual Destructors

  • If a class might be used as a base class, its destructor should always be declared virtual.
  • If the destructor is not virtual, deleting a derived class object through a base class pointer will only call the base class’s destructor, potentially leading to memory leaks.
  • Declaring the base class destructor as virtual ensures that both the derived and base class destructors are called in the correct order (derived first, then base).

🗊 Program 15-15

 #include <iostream>
 using namespace std;

 class Animal
 {
 public:
     Animal()
         { cout << "Animal constructor executing.\n"; }

     ~Animal()
         { cout << "Animal destructor executing.\n"; }
 };

 class Dog : public Animal
 {
 public:
     Dog() : Animal()
         { cout << "Dog constructor executing.\n"; }

     ~Dog()
         { cout << "Dog destructor executing.\n"; }
 };


 int main()
 {
     Animal *myAnimal = new Dog;

     delete myAnimal;
     return 0;
 }

💻 Program Output




🗊 Program 15-16

 #include <iostream>
 using namespace std;

 class Animal
 {
 public:
     Animal()
         { cout << "Animal constructor executing.\n"; }

     virtual ~Animal()
         { cout << "Animal destructor executing.\n"; }
 };

 class Dog : public Animal
 {
 public:
     Dog() : Animal()
         { cout << "Dog constructor executing.\n"; }

     ~Dog()
         { cout << "Dog destructor executing.\n"; }
 };


 int main()
 {
     Animal *myAnimal = new Dog;

     delete myAnimal;
     return 0;
 }

💻 Program Output





  • Good practice: Any class with a virtual function should also have a virtual destructor.

C++ 11’s override and final Key Words

  • The override keyword can be added to a derived class function to ensure it is correctly overriding a base class virtual function. The compiler will issue an error if the function signature does not match a base class virtual function.
  • This helps catch subtle bugs where a function in the derived class was intended to override but instead overloads the base class function due to a different signature.
  • The final keyword can be used to prevent a virtual function from being overridden in any subsequent derived classes.

🗊 Program 15-17

 #include <iostream>
 using namespace std;

 class Base
 {
 public:
     virtual void functionA(int arg) const
         { cout << "This is Base::functionA" << endl; }
 };

 class Derived : public Base
 {
 public:
     virtual void functionA(long arg) const
         { cout << "This is Derived::functionA" << endl; }
 };

 int main()
 {
     Base *b = new Derived();
     Derived *d = new Derived();

     b->functionA(99);
     d->functionA(99);

     return 0;
 }

💻 Program Output



🗊 Program 15-18

 #include <iostream>
 using namespace std;

 class Base
 {
 public:
     virtual void functionA(int arg) const
     { cout << "This is Base::functionA" << endl; }
 };

 class Derived : public Base
 {
 public:
     virtual void functionA(int arg) const override
     { cout << "This is Derived::functionA" << endl; }
 };

 int main()
 {
     Base *b = new Derived();
     Derived *d = new Derived();

     b->functionA(99);
     d->functionA(99);

     return 0;
 }

💻 Program Output




15.7 Abstract Base Classes and Pure Virtual Functions

Concept:
  • An abstract base class cannot be instantiated and is used only as a base for other classes.
  • A class becomes abstract when it contains at least one pure virtual function.
  • A pure virtual function must be overridden by any derived class that is to be instantiated.
  • An abstract base class represents a generic concept from which more specific classes are derived.
  • A pure virtual function is declared with = 0 at the end of its prototype and has no definition in the base class.
virtual void showInfo() = 0;
  • The compiler will generate an error if you try to create an object of an abstract base class.
  • The following Student class is an abstract base class because it contains a pure virtual function, getRemainingHours.
Contents of Student.h
 #ifndef STUDENT_H
 #define STUDENT_H
 #include <string>
 using namespace std;

 class Student
 {
 protected:
     string name;       
     string idNumber;   
     int yearAdmitted;  
 public:
     Student()
         { name = "";
           idNumber = "";
           yearAdmitted = 0; }

     Student(string n, string id, int year)
             {  set(n, id, year); }

     void set(string n, string id, int year)
         { name = n;               
           idNumber = id;          
           yearAdmitted = year; }  

     const string getName() const
         { return name; }

     const string getIdNum() const
         { return idNumber; }

     int getYearAdmitted() const
         { return yearAdmitted; }

     virtual int getRemainingHours() const = 0;
 };
 #endif
  • The getRemainingHours function is made pure virtual because its implementation depends on the student’s major, which will be specified in a derived class.
  • The CsStudent class, derived from Student, provides a concrete implementation for getRemainingHours.
Contents of CsStudent.h
 #ifndef CSSTUDENT_H
 #define CSSTUDENT_H
 #include "Student.h"

 const int MATH_HOURS = 20;    
 const int CS_HOURS = 40;      
 const int GEN_ED_HOURS = 60;  

 class CsStudent : public Student
 {
 private:
     int mathHours;     
     int csHours;       
     int genEdHours;    

 public:
     CsStudent() : Student()
         { mathHours = 0;
           csHours = 0;
           genEdHours = 0; }

     CsStudent(string n, string id, int year) :
        Student(n, id, year)
        { mathHours = 0;
          csHours = 0;
          genEdHours = 0; }

     void setMathHours(int mh)
        { mathHours = mh; }

     void setCsHours(int csh)
        { csHours = csh; }

     void setGenEdHours(int geh)
        { genEdHours = geh; }

     virtual int getRemainingHours() const;
 };
 #endif
Contents of CsStudent.cpp
 #include <iostream>
 #include "CsStudent.h"
 using namespace std;


 int CsStudent::getRemainingHours() const
 {
     int reqHours,    
     remainingHours;  

     reqHours = MATH_HOURS + CS_HOURS + GEN_ED_HOURS;

     remainingHours = reqHours - (mathHours + csHours +
                     genEdHours);

     return remainingHours;
 }

🗊 Program 15-19

 #include <iostream>
 #include "CsStudent.h"
 using namespace std;

 int main()
 {
     CsStudent student("Jennifer Haynes", "167W98337", 2006);

     student.setMathHours(12);    
     student.setCsHours(20);      
     student.setGenEdHours(40);   

     cout << "The student " << student.getName()
          << " needs to take " << student.getRemainingHours()
          << " more hours to graduate.\n";

     return 0;
 }

💻 Program Output


  • Key points:
    • A class with a pure virtual function is abstract.
    • Abstract classes cannot be instantiated.
    • Pure virtual functions must be overridden in a derived class for that derived class to be non-abstract and instantiable.

Checkpoint

  1. 15.9 Explain the difference between overloading a function and redefining a function.

  2. 15.10 Explain the difference between static binding and dynamic binding.

  3. 15.11 Are virtual functions statically bound or dynamically bound?

  4. 15.12 What will the following program display?

    #include <iostream.>
    using namespace std;
    
    class First
    {
    protected:
       int a;
    public:
       First(int x = 1)
          { a = x; }
       int getVal()
          { return a; }
    };
    
    class Second : public First
    {
    private:
       int b;
    
    public:
       Second(int y = 5)
          { b = y; }
       int getVal()
          { return b; }
    };
    
    int main()
    {
       First object1;
       Second object2;
    
       cout << object1.getVal() << endl;
       cout << object2.getVal() << endl;
       return 0;
    }
  5. 15.13 What will the following program display?

    #include <iostream>
    using namespace std;
    
    class First
    {
    protected:
       int a;
    public:
       First(int x = 1)
          { a = x; }
    
       void twist()
          { a *= 2; }
       int getVal()
          { twist(); return a; }
    };
    
    class Second : public First
    {
    private:
       int b;
    public:
       Second(int y = 5)
          { b = y; }
       void twist()
          { b *= 10; }
    };
    
    int main()
    {
       First object1;
       Second object2;
    
       cout << object1.getVal() << endl;
       cout << object2.getVal() << endl;
       return 0;
    }
  6. 15.14 What will the following program display?

    #include <iostream>
    using namespace std;
    
    class First
    {
    protected:
       int a;
    public:
       First(int x = 1)
          { a = x; }
    
       virtual void twist()
          { a *= 2; }
    
       int getVal()
          { twist(); return a; }
    };
    
    class Second : public First
    {
    private:
       int b;
    public:
       Second(int y = 5)
          { b = y; }
       virtual void twist()
          { b *= 10; }
    };
    
    int main()
    {
       First object1;
       Second object2;
       cout << object1.getVal() << endl;
       cout << object2.getVal() << endl;
       return 0;
    }
  7. 15.15 What will the following program display?

    #include <iostream>
    using namespace std;
    
    class Base
    {
    protected:
       int baseVar;
    public:
       Base(int val = 2)
          { baseVar = val; }
       int getVar()
          { return baseVar; }
    };
    
    class Derived : public Base
    {
    private:
       int derivedVar;
    public:
       Derived(int val = 100)
          { derivedVar = val; }
       int getVar()
          { return derivedVar; }
    };
    
    int main()
    {
       Base *optr = nullptr;
       Derived object;
    
       optr = &object;
       cout << optr->getVar() << endl;
       return 0;
    }

15.8 Multiple Inheritance

Concept:
  • Multiple inheritance allows a derived class to have two or more base classes.
  • Multiple inheritance is distinct from a chain of inheritance, where a class inherits from another class that is itself derived.

  • With multiple inheritance, a class directly inherits members from several base classes simultaneously.

  • As an example, a DateTime class can inherit from both a Date class and a Time class.

Contents of Date.h
 #ifndef DATE_H
 #define DATE_H

 class Date
 {
 protected:
     int day;
     int month;
     int year;
 public:
     Date(int d, int m, int y)
        { day = 1; month = 1; year = 1900; }

     Date(int d, int m, int y)
        { day = d; month = m; year = y; }

     int getDay() const
        { return day; }

     int getMonth() const
        { return month; }

     int getYear() const
        { return year; }
 };
 #endif
Contents of Time.h
 #ifndef TIME_H
 #define TIME_H

 class Time
 {
 protected:
     int hour;
     int min;
     int sec;
 public:
     Time()
        { hour = 0; min = 0; sec = 0; }

     Time(int h, int m, int s)
        { hour = h; min = m; sec = s; }

     int getHour() const
        { return hour; }

     int getMin() const
        { return min; }

     int getSec() const
        { return sec; }
 };
 #endif
  • The syntax for declaring a class with multiple bases involves listing each base class and its access specifier, separated by commas.
Contents of DateTime.h
 #ifndef DATETIME_H
 #define DATETIME_H
 #include <string>
 #include "Date.h"
 #include "Time.h"
 using namespace std;

 class DateTime : public Date, public Time
 {
 public:
     DateTime();

     DateTime(int, int, int, int, int, int);

     void showDateTime() const;
 };
 #endif
Contents of DateTime.cpp
 #include <iostream>
 #include <string>
 #include "DateTime.h"
 using namespace std;

 DateTime::DateTime() : Date(), Time()
 {}

 DateTime::DateTime(int dy, int mon, int yr, int hr, int mt, int sc) :
     Date(dy, mon, yr), Time(hr, mt, sc)
 {}

 void DateTime::showDateTime() const
 {
     cout << getMonth() << "/" << getDay() << "/" << getYear() << " ";

     cout << getHour() << ":" << getMin() << ":" << getSec() << endl;
 }
  • In the derived class’s constructor, calls to the base class constructors are listed after a colon, separated by commas.
  • Base class constructors are always called in the order they are listed in the class declaration, regardless of the order in the constructor’s initializer list.
  • Destructors are called in the reverse order of inheritance.

🗊 Program 15-20

 #include "DateTime.h"
 using namespace std;

 int main()
 {
     DateTime emptyDay;

     emptyDay.showDateTime();

     DateTime pastDay(2, 4, 1960, 5, 32, 27);

     pastDay.showDateTime();
     return 0;
 }

💻 Program Output



Note:
  • Multiple inheritance can lead to ambiguity if two or more base classes have members with the same name.
  • To resolve this, the derived class should redefine or override the ambiguous functions and use the scope resolution operator (::) to specify which base class member to access.

Checkpoint

  1. 15.16 Does the following diagram depict multiple inheritance or a chain of inheritance?

  2. 15.17 Does the following diagram depict multiple inheritance or a chain of inheritance?

  3. 15.18 Examine the following classes. The table lists the variables that are members of the Third class (some are inherited). Complete the table by filling in the access specification each member will have in the Third class. Write “inaccessible” if a member is inaccessible to the Third class.

    class First
    {
       private:
          int a;
       protected:
          double b;
       public:
          long c;
    };
    class Second : protected First
    {
       private:
          int d;
       protected:
          double e;
       public:
          long f;
    };
    class Third : public Second
    {
       private:
          int g;
       protected:
          double h;
       public:
          long i;
    };
    Member Variable Access Specification in Third Class
    a
    b
    c
    d
    e
    f
    g
    h
    i
  4. 15.19 Examine the following class declarations:

    class Van
    {
    protected:
       int passengers;
    public:
       Van(int p)
       { passengers = p; }
    };
    class FourByFour
    {
    protected:
       double cargoWeight;
    public:
       FourByFour(float w)
          { cargoWeight = w; }
    };

Write the declaration of a class named SportUtility. The class should be derived from both the Van and FourByFour classes above. (This should be a case of multiple inheritance, where both Van and FourByFour are base classes.)

Review Questions and Exercises

Short Answer

  1. What is an “is a” relationship?

  2. A program uses two classes: Dog and Poodle. Which class is the base class, and which is the derived class?

  3. How does base class access specification differ from class member access specification?

  4. What is the difference between a protected class member and a private class member?

  5. Can a derived class ever directly access the private members of its base class?

  6. Which constructor is called first, that of the derived class or the base class?

  7. What is the difference between redefining a base class function and overriding a base class function?

  8. When does static binding take place? When does dynamic binding take place?

  9. What is an abstract base class?

  10. A program has a class Potato, which is derived from the class Vegetable, which is derived from the class Food. Is this an example of multiple inheritance? Why or why not?

  11. What base class is named in the line below?

    class Pet : public Dog
  12. What derived class is named in the line below?

    class Pet : public Dog
  13. What is the class access specification of the base class named below?

    class Pet : public Dog
  14. What is the class access specification of the base class named below?

    class Pet : Fish
  15. Protected members of a base class are like ____________________ members, except they may be accessed by derived classes.

  16. Complete the table on the next page by filling in private, protected, public, or inaccessible in the right-hand column:

    In a private base class, this base class MEMBER access specification… …becomes this access specification in the derived class.
    private
    protected
    public
  17. Complete the table below by filling in private, protected, public, or inaccessible in the right-hand column:

    In a protected base class, this base class MEMBER access specification… …becomes this access specification in the derived class.
    private
    protected
    public
  18. Complete the table below by filling in private, protected, public, or inaccessible in the right-hand column:

    In a public base class, this base class MEMBER access specification… …becomes this access specification in the derived class.
    private
    protected
    public

Fill-in-the-Blank

  1. A derived class inherits the ____________________ of its base class.

  2. When both a base class and a derived class have constructors, the base class’s constructor is called ____________________ (first/last).

  3. When both a base class and a derived class have destructors, the base class’s constructor is called ____________________ (first/last).

  4. An overridden base class function may be called by a function in a derived class by using the ____________________ operator.

  5. When a derived class redefines a function in a base class, which version of the function do objects that are defined of the base class call? ____________________

  6. A(n) ____________________ member function in a base class expects to be overridden in a derived class.

  7. ____________________ binding is when the compiler binds member function calls at compile time.

  8. ____________________ binding is when a function call is bound at runtime.

  9. ____________________ is when member functions in a class hierarchy behave differently, depending upon which object performs the call.

  10. When a pointer to a base class is made to point to a derived class, the pointer ignores any ____________________ the derived class performs, unless the function is ____________________.

  11. A(n) ____________________ class cannot be instantiated.

  12. A(n) ____________________ function has no body, or definition, in the class in which it is declared.

  13. A(n) ____________________ of inheritance is where one class is derived from a second class, which in turn is derived from a third class.

  14. ____________________ is where a derived class has two or more base classes.

  15. In multiple inheritance, the derived class should always ____________________ a function that has the same name in more than one base class.

Algorithm Workbench

  1. Write the first line of the declaration for a Poodle class. The class should be derived from the Dog class with public base class access.

  2. Write the first line of the declaration for a SoundSystem class. Use multiple inheritance to base the class on the CDplayer class, the Tuner class, and the MP3Player class. Use public base class access in all cases.

  3. Suppose a class named Tiger is derived from both the Felis class and the Carnivore class. Here is the first line of the Tiger class declaration:

    class Tiger : public Felis, public Carnivore

    Here is the function header for the Tiger constructor:

    Tiger(int x, int y) : Carnivore(x), Felis(y)

    Which base class constructor is called first, Carnivore or Felis?

  4. Write the declaration for class B. The class’s members should be as follows:

    • m: an integer. This variable should not be accessible to code outside the class or to member functions in any class derived from class B.

    • n: an integer. This variable should not be accessible to code outside the class, but should be accessible to member functions in any class derived from class B.

    • setM, getM, setN, and getN: These are the set and get functions for the member variables m and n. These functions should be accessible to code outside the class.

    • calc: a public virtual member function that returns the value of m times n.

    Next, write the declaration for class D, which is derived from class B. The class’s members should be as follows:

    • q: a float. This variable should not be accessible to code outside the class but should be accessible to member functions in any class derived from class D.

    • r: a float. This variable should not be accessible to code outside the class, but should be accessible to member functions in any class derived from class D.

    • setQ, getQ, setR, and getR: These are the set and get functions for the member variables q and r. These functions should be accessible to code outside the class.

    • calc: a public member function that overrides the base class calc function. This function should return the value of q times r.

True or False

  1. T F The base class’s access specification affects the way base class member functions may access base class member variables.

  2. T F The base class’s access specification affects the way the derived class inherits members of the base class.

  3. T F Private members of a private base class become inaccessible to the derived class.

  4. T F Public members of a private base class become private members of the derived class.

  5. T F Protected members of a private base class become public members of the derived class.

  6. T F Public members of a protected base class become private members of the derived class.

  7. T F Private members of a protected base class become inaccessible to the derived class.

  8. T F Protected members of a public base class become public members of the derived class.

  9. T F The base class constructor is called after the derived class constructor.

  10. T F The base class destructor is called after the derived class destructor.

  11. T F It isn’t possible for a base class to have more than one constructor.

  12. T F Arguments are passed to the base class constructor by the derived class constructor.

  13. T F A member function of a derived class may not have the same name as a member function of the base class.

  14. T F Pointers to a base class may be assigned the address of a derived class object.

  15. T F A base class may not be derived from another class.

Find the Errors

Each of the class declarations and/or member function definitions below has errors. Find as many as you can.

  1. class Car, public Vehicle
    {
       public:
          Car();
          ~Car();
       protected:
          int passengers;
    }
  2. class Truck, public : Vehicle, protected
    {
       private:
          double cargoWeight;
       public:
          Truck();
          ~Truck();
    };
  3. class SnowMobile : Vehicle
    {
       protected:
          int horsePower;
          double weight;
       public:
          SnowMobile(int h, double w), Vehicle(h)
             { horsePower = h; }
          ~SnowMobile();
    };
  4. class Table : public Furniture
    {
       protected:
          int numSeats;
       public:
          Table(int n) : Furniture(numSeats)
             { numSeats = n; }
          ~Table();
    };
  5. class Tank : public Cylinder
    {
       private:
          int fuelType;
          double gallons;
       public:
          Tank();
          ~Tank();
          void setContents(double);
          void setContents(double);
    };
  6. class Three : public Two : public One
    {
       protected:
          int x;
       public:
          Three(int a, int b, int c), Two(b), Three(c)
             { x = a; }
          ~Three();
    };

Programming Challenges

  1. Employee and ProductionWorker Classes

    Solving the Employee and ProductionWorker Classes Problem

    Design a class named Employee. The class should keep the following information:

    • Employee name

    • Employee number

    • Hire date

    Write one or more constructors, and the appropriate accessor and mutator functions, for the class.

    Next, write a class named ProductionWorker that is derived from the Employee class. The ProductionWorker class should have member variables to hold the following information:

    • Shift (an integer)

    • Hourly pay rate (a double)

    The workday is divided into two shifts: day and night. The shift variable will hold an integer value representing the shift that the employee works. The day shift is shift 1, and the night shift is shift 2. Write one or more constructors, and the appropriate accessor and mutator functions, for the class. Demonstrate the classes by writing a program that uses a ProductionWorker object.

  2. ShiftSupervisor Class

    In a particular factory, a shift supervisor is a salaried employee who supervises a shift. In addition to a salary, the shift supervisor earns a yearly bonus when his or her shift meets production goals. Design a ShiftSupervisor class that is derived from the Employee class you created in Programming Challenge 1 (Employee and Production Worker Classes). The ShiftSupervisor class should have a member variable that holds the annual salary, and a member variable that holds the annual production bonus that a shift supervisor has earned. Write one or more constructors and the appropriate accessor and mutator functions for the class. Demonstrate the class by writing a program that uses a ShiftSupervisor object.

  3. TeamLeader Class

    In a particular factory, a team leader is an hourly paid production worker who leads a small team. In addition to hourly pay, team leaders earn a fixed monthly bonus. Team leaders are required to attend a minimum number of hours of training per year. Design a TeamLeader class that extends the ProductionWorker class you designed in Programming Challenge 1 (Employee and Production Worker Classes). The TeamLeader class should have member variables for the monthly bonus amount, the required number of training hours, and the number of training hours that the team leader has attended. Write one or more constructors and the appropriate accessor and mutator functions for the class. Demonstrate the class by writing a program that uses a TeamLeader object.

  4. Time Format

    In Program 15-20, the file Time.h contains a Time class. Design a class called MilTime that is derived from the Time class. The MilTime class should convert time in military (24-hour) format to the standard time format used by the Time class. The class should have the following member variables:

    milHours: Contains the hour in 24-hour format. For example, 1:00 p.m. would be stored as 1300 hours, and 4:30 p.m. would be stored as 1630 hours.
    milSeconds: Contains the seconds in standard format.

    The class should have the following member functions:

    Constructor: The constructor should accept arguments for the hour and seconds, in military format. The time should then be converted to standard time and stored in the hours, min, and sec variables of the Time class.
    setTime: Accepts arguments to be stored in the milHours and milSeconds variables. The time should then be converted to standard time and stored in the hours, min, and sec variables of the Time class.
    getHour: Returns the hour in military format.
    getStandHr: Returns the hour in standard format.

    Demonstrate the class in a program that asks the user to enter the time in military format. The program should then display the time in both military and standard format.

    Input Validation: The MilTime class should not accept hours greater than 2359, or less than 0. It should not accept seconds greater than 59 or less than 0.

  5. Time Clock

    Design a class named TimeClock. The class should be derived from the MilTime class you designed in Programming Challenge 4 (Time Format). The class should allow the programmer to pass two times to it: starting time and ending time. The class should have a member function that returns the amount of time elapsed between the two times. For example, if the starting time is 900 hours (9:00 a.m.), and the ending time is 1300 hours (1:00 p.m.), the elapsed time is 4 hours.

    Input Validation: The class should not accept hours greater than 2359 or less than 0.

  6. Essay Class

    Design an Essay class that is derived from the GradedActivity class presented in this chapter. The Essay class should determine the grade a student receives on an essay. The student’s essay score can be up to 100, and is determined in the following manner:

    • Grammar: 30 points

    • Spelling: 20 points

    • Correct length: 20 points

    • Content: 30 points

    Demonstrate the class in a simple program.

  7. PersonData and CustomerData Classes

    Design a class named PersonData with the following member variables:

    • lastName

    • firstName

    • address

    • city

    • state

    • zip

    • phone

    Write the appropriate accessor and mutator functions for these member variables.

    Next, design a class named CustomerData, which is derived from the PersonData class. The CustomerData class should have the following member variables:

    • customerNumber

    • mailingList

    The customerNumber variable will be used to hold a unique integer for each customer. The mailingList variable should be a bool. It will be set to true if the customer wishes to be on a mailing list, or false if the customer does not wish to be on a mailing list. Write appropriate accessor and mutator functions for these member variables. Demonstrate an object of the CustomerData class in a simple program.

  8. PreferredCustomer Class

    A retail store has a preferred customer plan where customers may earn discounts on all their purchases. The amount of a customer’s discount is determined by the amount of the customer’s cumulative purchases in the store.

    • When a preferred customer spends $500, he or she gets a 5 percent discount on all future purchases.

    • When a preferred customer spends $1,000, he or she gets a 6 percent discount on all future purchases.

    • When a preferred customer spends $1,500, he or she gets a 7 percent discount on all future purchases.

    • When a preferred customer spends $2,000 or more, he or she gets a 10 percent discount on all future purchases.

    Design a class named PreferredCustomer, which is derived from the CustomerData class you created in Programming Challenge 7. The PreferredCustomer class should have the following member variables:

    • purchasesAmount (a double)

    • discountLevel (a double)

    The purchasesAmount variable holds the total of a customer’s purchases to date. The discountLevel variable should be set to the correct discount percentage, according to the store’s preferred customer plan. Write appropriate member functions for this class and demonstrate it in a simple program.

    Input Validation: Do not accept negative values for any sales figures.

  9. File Filter

    A file filter reads an input file, transforms it in some way, and writes the results to an output file. Write an abstract file filter class that defines a pure virtual function for transforming a character. Create one derived class of your file filter class that performs encryption, another that transforms a file to all uppercase, and another that creates an unchanged copy of the original file. The class should have the following member function:

    void doFilter(ifstream &in, ofstream &out)

    This function should be called to perform the actual filtering. The member function for transforming a single character should have the prototype:

    char transform(char ch)

    The encryption class should have a constructor that takes an integer as an argument and uses it as the encryption key.

  10. File Double-Spacer

    Create a derived class of the abstract filter class of Programming Challenge 9 (File Filter) that double-spaces a file, that is, it inserts a blank line between any two lines of the file.

  11. Course Grades

    In a course, a teacher gives the following tests and assignments:

    • A lab activity that is observed by the teacher and assigned a numeric score.

    • A pass/fail exam that has ten questions. The minimum passing score is 70.

    • An essay that is assigned a numeric score.

    • A final exam that has 50 questions.

    Write a class named CourseGrades. The class should have a member named grades that is an array of GradedActivity pointers. The grades array should have four elements, one for each of the assignments previously described. The class should have the following member functions:

    setLab: This function should accept the address of a GradedActivity object as its argument. This object should already hold the student’s score for the lab activity. Element 0 of the grades array should reference this object.
    setPassFailExam: This function should accept the address of a PassFailExam object as its argument. This object should already hold the student’s score for the pass/fail exam. Element 1 of the grades array should reference this object.
    setEssay: This function should accept the address of an Essay object as its argument. (See Programming Challenge 6 for the Essay class. If you have not completed Programming Challenge 6, use a GradedActivity object instead.) This object should already hold the student’s score for the essay. Element 2 of the grades array should reference this object.
    setPassFailExam: This function should accept the address of a FinalExam object as its argument. This object should already hold the student’s score for the final exam. Element 3 of the grades array should reference this object.
    print: This function should display the numeric scores and grades for each element in the grades array.

    Demonstrate the class in a program.

  12. Ship, CruiseShip, and CargoShip Classes

    Design a Ship class that has the following members:

    • A member variable for the name of the ship (a string)

    • A member variable for the year that the ship was built (a string)

    • A constructor and appropriate accessors and mutators

    • A virtual print function that displays the ship’s name and the year it was built.

    Design a CruiseShip class that is derived from the Ship class. The CruiseShip class should have the following members:

    • A member variable for the maximum number of passengers (an int)

    • A constructor and appropriate accessors and mutators

    • A print function that overrides the print function in the base class. The CruiseShip class’s print function should display only the ship’s name and the maximum number of passengers.

    Design a CargoShip class that is derived from the Ship class. The CargoShip class should have the following members:

    • A member variable for the cargo capacity in tonnage (an int)

    • A constructor and appropriate accessors and mutators

    • A print function that overrides the print function in the base class. The CargoShip class’s print function should display only the ship’s name and the ship’s cargo capacity.

    Demonstrate the classes in a program that has an array of Ship pointers. The array elements should be initialized with the addresses of dynamically allocated Ship, CruiseShip, and CargoShip objects. (See Program 15-14, lines 17 through 22, for an example of how to do this.) The program should then step through the array, calling each object’s print function.

  13. Pure Abstract Base Class Project

    Define a pure abstract base class called BasicShape. The BasicShape class should have the following members:

    Private Member Variable:

    • area: A double used to hold the shape’s area.

    Public Member Functions:

    • getArea: This function should return the value in the member variable area.

    • calcArea:This function should be a pure virtual function.

    Next, define a class named Circle. It should be derived from the BasicShape class. It should have the following members:

    Private Member Variables:

    • centerX: a long integer used to hold the x coordinate of the circle’s center

    • centerY: a long integer used to hold the y coordinate of the circle’s center

    • radius: a double used to hold the circle’s radius

    Public Member Functions:

    • constructor: accepts values for centerX, centerY, and radius.Should call the overriddencalcArea function described below.

    • getCenterX: returns the value in centerX

    • getCenterY: returns the value in centerY

    • calcArea: calculates the area of the circle (area = 3.14159 * radius * radius) and stores the result in the inherited member area.

    Next, define a class named Rectangle. It should be derived from the BasicShape class. It should have the following members:

    Private Member Variables:

    • width: a long integer used to hold the width of the rectangle

    • length: a long integer used to hold the length of the rectangle

    Public Member Functions:

    • constructor: accepts values for width and length. Should call the overridden calcArea function described below.

    • getWidth: returns the value in width.

    • getLength: returns the value in length.

    • calcArea: calculates the area of the rectangle (area = length * width) and stores the result in the inherited member area.

    After you have created these classes, create a driver program that defines a Circle object and a Rectangle object. Demonstrate that each object properly calculates and reports its area.

Group Project

  1. Bank Accounts

    This program should be designed and written by a team of students. Here are some suggestions:

    • One or more students may work on a single class.

    • The requirements of the program should be analyzed so that each student is given about the same work load.

    • The parameters and return types of each function and class member function should be decided in advance.

    • The program will be best implemented as a multi-file program.

    Design a generic class to hold the following information about a bank account:

    • Balance

    • Number of deposits this month

    • Number of withdrawals

    • Annual interest rate

    • Monthly service charges

    The class should have the following member functions:

    constructor: Accepts arguments for the balance and annual interest rate.
    deposit: A virtual function that accepts an argument for the amount of the deposit. The function should add the argument to the account balance. It should also increment the variable holding the number of deposits.
    withdraw: A virtual function that accepts an argument for the amount of the withdrawal. The function should subtract the argument from the balance. It should also increment the variable holding the number of withdrawals.
    calcInt:

    A virtual function that updates the balance by calculating the monthly interest earned by the account, and adding this interest to the balance. This is performed by the following formulas:

    \begin{array}{l} {\text{Monthly}\,\text{Interest}\,\text{Rate}\, = \,\text{(Annual}\,\text{Interest}\,\text{Rate}\,\text{/}\,\text{12}} \\ {\text{Monthly}\,\text{Interest}\, = \,\text{Balance}\,*\,\text{Monthly}\,\text{Interest}\,\text{Rate}} \\ {\text{Balance}\, = \,\text{Balance}\, + \,\text{Monthly}\,\text{Interest}} \end{array}

    monthlyProc: A virtual function that subtracts the monthly service charges from the balance, calls the calcInt function, then sets the variables that hold the number of withdrawals, number of deposits, and monthly service charges to zero.

    Next, design a savings account class, derived from the generic account class. The savings account class should have the following additional member:

    • status (to represent an active or inactive account)

    If the balance of a savings account falls below $25, it becomes inactive. (The status member could be a flag variable.) No more withdrawals may be made until the balance is raised above $25, at which time the account becomes active again. The savings account class should have the following member functions:

    withdraw: A function that checks to see if the account is inactive before a withdrawal is made. (No withdrawal will be allowed if the account is not active.) A withdrawal is then made by calling the base class version of the function.
    deposit: A function that checks to see if the account is inactive before a deposit is made. If the account is inactive and the deposit brings the balance above $25, the account becomes active again. The deposit is then made by calling the base class version of the function.
    monthlyProc: Before the base class function is called, this function checks the number of withdrawals. If the number of withdrawals for the month is more than 4, a service charge of $1 for each withdrawal above 4 is added to the base class variable that holds the monthly service charges. (Don’t forget to check the account balance after the service charge is taken. If the balance falls below $25, the account becomes inactive.)

    Next, design a checking account class, also derived from the generic account class. It should have the following member functions:

    withdraw: Before the base class function is called, this function will determine if a withdrawal (a check written) will cause the balance to go below $0. If the balance goes below $0, a service charge of $15 will be taken from the account. (The withdrawal will not be made.) If there isn’t enough in the account to pay the service charge, the balance will become negative and the customer will owe the negative amount to the bank.
    monthlyProc: Before the base class function is called, this function adds the monthly fee of $5 plus $0.10 per withdrawal (check written) to the base class variable that holds the monthly service charges.

    Write a complete program that demonstrates these classes by asking the user to enter the amounts of deposits and withdrawals for a savings account and checking account. The program should display statistics for the month, including beginning balance, total amount of deposits, total amount of withdrawals, service charges, and ending balance.

    Note:

    You may need to add more member variables and functions to the classes than those listed above.