15.1 Inheritance Basics

If there is anything that we wish to change in the child, we should first examine it and see whether it is not something that could better be changed in ourselves.

CARL GUSTAV JUNG, The Integration of the Personality

One of the most powerful features of C++ is the use of inheritance to derive one class from another. Inheritance is the process by which a new class—known as a derived class—is created from another class, called the base class. A derived class automatically has all the member variables and functions that the base class has and can have additional member functions and/or additional member variables.

In Chapter 10, we noted that saying that class D is derived from another class B means that class D has all the features of class B and some extra, added features as well. When a class D is derived from a class B, we say that B is the base class and D is the derived class. We also say that D is the child class and B is the parent class.1

As an example to illustrate the usefulness of inheritance, imagine that you’ve set up a home automation system where your garage door and furnace thermostat are networked and accessible from your computer. You would like to control and interrogate the status of these devices (e.g., door is open, thermostat set to 80 degrees) from your computer. This would be much easier to accomplish if there was a consistent interface for these disparate devices. Inheritance lets us do this while providing a way to organize our code without duplication.

First, consider the general concept of a device in the home automation system. Every device must have a model and serial number. Perhaps every device also has a way to query its status. We could model this with a Device class that has variables for the model and serial number, and a function for the status. The idea is that this class contains functions and properties that are common to every possible device.

Second, consider the garage door. This is a specific type of device in the automation system. In addition to having a model, serial number, and way to query its status like every other device, the garage door device also has a specific function to open or close the door. We can model the garage door with a DoorDevice class. We will need to add an openClose() function to this class. The DoorDevice class is also where we would know how to return the status of the device. At the level of the generic Device class we don’t have the needed information to return the status of a specific device because at that level we don’t even know what kind of device we are working with. While we need to add functions to DoorDevice for the status and to open/close the door, it would be nice if we didn’t have to duplicate the variables and code to manipulate the model and serial number that we wrote for the Device class.

Similarly, the thermostat device will also have a model, serial number, and way to query its status in addition to a function to set the temperature. We can define a ThermostatDevice class with functions to set the temperature and return the status of the device, but it would be nice if we didn’t again have to duplicate the variables and code to manipulate the model and serial number that we wrote for the Device class!

We can solve this problem with inheritance. In this case, DoorDevice “IS-A” Device and ThermostatDevice “IS-A” Device. By defining DoorDevice and ThermostatDevice as derived classes from Device, then these classes (if the programmer specifies it) have access to the model and serial number defined in Device and we don’t need to re-write any code in the Device class that deals with these variables. At the same time we can add specific code that is unique to our derived classes. The relationship between these classes is illustrated in Display 15.1.

Display 15.1 Example Inheritance Hierarchy for Home Automation Devices

An object of type DoorDevice or ThermostatDevice includes functions and variables defined in Device, such as model and serialNumber.

The status() function can be overridden. If a DoorDevice object is treated like a Device object, then calling status() will invoke DoorDevice's status() function, not Device's status() function. This is necessary when the Device class doesn’t know what to return as a status and only the derived classes can return the information.

A diagram showing an example inheritance hierarchy for home automation devices.
Figure 15.1 Full Alternative Text

Once the inheritance relationship is defined, then if we create an object of type DoorDevice or ThermostatDevice we will have access to functions and variables defined in Device. For example, if thermostat is a variable of type ThermostatDevice then we could access thermostat.model if model is a public string variable in the Device class. This saves us the work of redefining the code and variables from the Device class.

We can specify the status() function to behave a bit differently. When we define the same function in both the base and derived classses then we will see later in the chapter that we have two options: redefine the function or override the function. In this case we want to override the function. If we had an object thermostat of type ThermostatDevice, but then treat thermostat instead like it is of type Device (for example, by passing thermostat to a function where the parameter is defined to be of type Device), then invoking status() will call the definition associated with ThermostatDevice rather than the definition associated with Device. This behavior is important in this case because the Device class doesn’t know what to return as the status! This topic is explored in more detail in Section 15.3.

For another example where inheritance can be applied, consider the CD account in Chapter 10. We discussed how a CD account is a more specialized version of a savings account. By deriving the class CDAccount from SavingsAccount, we automatically inherit all of the SavingsAccount public functions and variables when we create a CDAccount object. C++ uses inheritance in predefined classes as well. In using streams for file I/O, the predefined class ifstream is derived from the (predefined) class istream by adding member functions such as open and close. The stream cin belongs to the class of all input streams (that is, the class istream), but it does not belong to the class of input-file streams (that is, does not belong to ifstream), partly because it lacks the member functions open and close of the derived class ifstream.

Derived Classes

Suppose we are designing a record-keeping program that has records for salaried employees and hourly employees. There is a natural hierarchy for grouping these classes. These are all classes of people who share the property of being employees.

Employees who are paid an hourly wage are one subset of employees. Another subset consists of employees who are paid a fixed wage each month or each week. Although the program may not need any type corresponding to the set of all employees, thinking in terms of the more general concept of employees can be useful. For example, all employees have names and Social Security numbers, and the member functions for setting and changing names and Social Security numbers will be the same for salaried and hourly employees.

Within C++ you can define a class called Employee that includes all employees, whether salaried or hourly, and then use this class to define classes for hourly employees and salaried employees. Displays 15.2 and 15.3 show one possible definition for the class Employee.

Display 15.2 Interface for the Base Class Employee

 1    //This is the header file employee.h.
 2    //This is the interface for the class Employee.
 3    //This is primarily intended to be used as a base class to derive
 4    //classes for different kinds of employees.
 5    #ifndef EMPLOYEE_H
 6    #define EMPLOYEE_H

 7    #include <string>

 8    using namespace std;

 9    namespace employeessavitch
10    {

11        class Employee
12        {
13        public:
14            Employee( );
15            Employee(string theName, string theSSN);
16            string getName( ) const;
17            string getSSN( ) const;
18            double getNetPay( ) const;
19            void setName(string newName);
20            void setSSN(string newSSN);
21            void setNetPay(double newNetPay);
22            void printCheck( ) const;
23        private:
24            string name;
25            string ssn;
26            double netPay;
27        };

28    }//employeessavitch
29    #endif //EMPLOYEE_H

Display 15.3 Implementation for the Base Class Employee

 1    //This is the file: employee.cpp.
 2    //This is the implementation for the class Employee.
 3    //The interface for the class Employee is in the header file employee.h.
 4    #include <string>
 5    #include <cstdlib>
 6    #include <iostream>
 7    #include "employee.h"
 8    using namespace std;

 9    namespace employeessavitch
10    {
11        Employee::Employee( ) : name("No name yet"), ssn("No number yet"), netPay(0)
12        {
13            //deliberately empty
14        }

15        Employee::Employee(string theName, string theNumber)
16           : name(theName), ssn(theNumber), netPay(0)
17        {
18            //deliberately empty
19        }

20        string Employee::getName( ) const
21        {
22            return name;
23        }

24        string Employee::getSSN( ) const
25        {
26            return ssn;
27        }
28     
29        double Employee::getNetPay( ) const
30        {
31            return netPay;
32        }

33        void Employee::setName(string newName)
34        {
35            name = newName;
36        }
37        void Employee::setSSN(string newSSN)
38        {
39            ssn = newSSN;
40        }
41        void Employee::setNetPay (double newNetPay)
42        {
43            netPay = newNetPay;
44        }

45        void Employee::printCheck( ) const
46        {
47            cout << "\nERROR: printCheck FUNCTION CALLED FOR AN \n"
48                 << "UNDIFFERENTIATED EMPLOYEE. Aborting the program.\n"
49                 << "Check with the author of the program about this bug.\n";
50            exit(1);
51        }

52    }//employeessavitch

You can have an (undifferentiated) Employee object, but our reason for defining the class Employee is so that we can define derived classes for different kinds of employees. In particular, the function printCheck will always have its definition changed in derived classes so that different kinds of employees can have different kinds of checks. This is reflected in the definition of the function printCheck for the class Employee (Display 15.3). It makes little sense to print a check for such an (undifferentiated) Employee. We know nothing about this employee’s salary details. Consequently, we implemented the function printCheck of the class Employee so that the program stops with an error message if printCheck is called for a base class Employee object. As you will see, derived classes will have enough information to redefine the function printCheck to produce meaningful employee checks.

A class that is derived from the class Employee will automatically have all the member variables of the class Employee (name, ssn, and netPay). A class that is derived from the class Employee will also have all the member functions of the class Employee, such as printCheck, getName, setName, and the other member functions listed in Display 15.2. This is usually expressed by saying that the derived class inherits the member variables and member functions.

The interface files with the class definitions of two derived classes of the class Employee are given in Displays 15.4 (HourlyEmployee) and 15.5 (SalariedEmployee). We have placed the class Employee and the two derived classes in the same namespace. C++ does not require that they be in the same namespace, but since they are related classes, it makes sense to put them there. We will first discuss the derived class HourlyEmployee given in Display 15.4.

Display 15.4 Interface for the Derived Class HourlyEmployee

An illustration shows a code segment with “void printCheck( )” annotated as “You only list the declaration of an inherited member function if you want to change the definition of the function.”

Display 15.5 Interface for the Derived Class SalariedEmployee

 1    //This is the header file salariedemployee.h.
 2    //This is the interface for the class SalariedEmployee.
 3    #ifndef SALARIEDEMPLOYEE_H
 4    #define SALARIEDEMPLOYEE_H

 5    #include <string>
 6    #include "employee.h"

 7    using namespace std;

 8    namespace employeessavitch
 9    {

10        class SalariedEmployee : public Employee
11        {
12        public:
13            SalariedEmployee( );
14            SalariedEmployee (string theName, string theSSN,
15                                    double theWeeklySalary);
16            double getSalary( ) const;
17            void setSalary(double newSalary);
18            void printCheck( );
19        private:
20            double salary;//weekly
21        };

22    }//employeessavitch 
23    #endif //SALARIEDEMPLOYEE_H

Note that the definition of a derived class begins like any other class definition but adds a colon, the reserved word public, and the name of the base class to the first line of the class definition, as in the following (from Display 15.4):

class HourlyEmployee : public Employee
{

By using the keyword public the derived class (such as HourlyEmployee) automatically receives all the public member variables and member functions of the base class (such as Employee). We can also add additional member variables and member functions to the derived class.

The definition of the class HourlyEmployee does not mention the member variables name, ssn, and netPay, but every object of the class HourlyEmployee has member variables named name, ssn, and netPay. These member variables are inherited from the class Employee. The class HourlyEmployee declares two additional member variables named wageRate and hours. Thus, every object of the class HourlyEmployee has five member variables named name, ssn, netPay, wageRate, and hours. Note that the definition of a derived class (such as HourlyEmployee) only lists the added member variables. The member variables defined in the base class are not mentioned. They are provided automatically to the derived class.

Just as it inherits the member variables of the class Employee, the class HourlyEmployee inherits all the member functions from the class Employee. So, the class HourlyEmployee inherits the member functions getName, getSSN, getNetPay, setName, setSSN, setNetPay, and printCheck from the class Employee.

In addition to the inherited member variables and member functions, a derived class can add new member variables and new member functions. The new member variables and the declarations for the new member functions are listed in the class definition. For example, the derived class HourlyEmployee adds the two member variables wageRate and hours, and it adds the new member functions named setRate, getRate, setHours, and getHours. This is shown in Display 15.4. Note that you do not give the declarations of the inherited member functions except for those whose definitions you want to change, which is the reason we list only the member function printCheck from the base class Employee. For now, do not worry about the details of the constructor definition for the derived class. We will discuss constructors in the next subsection.

In the implementation file for the derived class, such as the implementation of HourlyEmployee in Display 15.6, you give the definitions of all the added member functions. Note that you do not give definitions for the inherited member functions unless the definition of the member function is changed in the derived class, a point we discuss next.

Display 15.6 Implementation for the Derived Class HourlyEmployee

An illustration shows a code segment illustrating the implementation of the derived class “HourlyEmployee.”

The definition of an inherited member function can be changed in the definition of a derived class so that it has a meaning in the derived class that is different from what it is in the base class. This is called redefining the inherited member function. For example, the member function printCheck( ) is redefined in the definition of the derived class HourlyEmployee. To redefine a member function definition, simply list it in the class definition and give it a new definition, just as you would do with a member function that is added in the derived class. This is illustrated by the redefined function printCheck( ) of the class HourlyEmployee (Displays 15.4 and 15.6).

SalariedEmployee is another example of a derived class of the class Employee. The interface for the class SalariedEmployee is given in Display 15.5. An object declared to be of type SalariedEmployee has all the member functions and member variables of Employee and the new members given in the definition of the class SalariedEmployee. This is true even though the class SalariedEmployee lists none of the inherited variables and only lists one function from the class Employee, namely, the function printCheck, which will have its definition changed in SalariedEmployee. The class SalariedEmployee, nonetheless, has the three member variables name, ssn, and netPay, as well as the member variable salary. Notice that you do not have to declare the member variables and member functions of the class Employee, such as name and setName, in order for a SalariedEmployee to have these members. The class SalariedEmployee gets these inherited members automatically without the programmer doing anything.

Note that the class Employee has all the code that is common to the two classes HourlyEmployee and SalariedEmployee. This saves you the trouble of writing identical code two times, once for the class HourlyEmployee and once for the class SalariedEmployee. Inheritance allows you to reuse the code in the class Employee.

Constructors in Derived Classes

A constructor in a base class is not inherited in the derived class, but you can invoke a constructor of the base class within the definition of a derived class constructor, and that is all you need or normally want. A constructor for a derived class uses a constructor from the base class in a special way. A constructor for the base class initializes all the data inherited from the base class. Thus, a constructor for a derived class begins with an invocation of a constructor for the base class.

There is a special syntax for invoking the base class constructor that is illustrated by the constructor definitions for the class HourlyEmployee given in Display 15.6. In what follows we have reproduced (with minor changes in the line breaks to make it fit the text column) one of the constructor definitions for the class HourlyEmployee taken from that display:

HourlyEmployee::HourlyEmployee(string theName,
     string theNumber, double theWageRate,
     double theHours)
     : Employee(theName, theNumber),
                wageRate(theWageRate), hours(theHours)
{
     //deliberately empty
}

The portion after the colon is the initialization section of the constructor definition for the constructor HourlyEmployee::HourlyEmployee. The part Employee(theName, theNumber) is an invocation of the two-argument constructor for the base class Employee. Note that the syntax for invoking the base class constructor is analogous to the syntax used to set member variables: The entry wageRate(theWageRate) sets the value of the member variable wageRate to theWageRate; the entry Employee(theName, theNumber) invokes the base class constructor Employee with the arguments theName and theNumber. Since all the work is done in the initialization section, the body of the constructor definition is empty.

Here we reproduce the other constructor for the class HourlyEmployee from Display 15.6:

HourlyEmployee::HourlyEmployee( ) : Employee( ), wageRate(0),
                hours(0)
{
 //deliberately empty 
}

In this constructor definition the default (zero-argument) version of the base class constructor is called to initialize the inherited member variables. You should always include an invocation of one of the base class constructors in the initialization section of a derived class constructor.

If a constructor definition for a derived class does not include an invocation of a constructor for the base class, then the default (zero-argument) version of the base class constructor will be invoked automatically. So, the following definition of the default constructor for the class HourlyEmployee (with Employee( ) omitted) is equivalent to the version we just discussed:

HourlyEmployee::HourlyEmployee( ) : wageRate(0), hours(0)
{
 //deliberately empty
}

However, we prefer to always explicitly include a call to a base class constructor, even if it would be invoked automatically.

A derived class object has all the member variables of the base class. When a derived class constructor is called, these member variables need to be allocated memory and should be initialized. This allocation of memory for the inherited member variables must be done by a constructor for the base class, and the base class constructor is the most convenient place to initialize these inherited member variables. That is why you should always include a call to one of the base class constructors when you define a constructor for a derived class. If you do not include a call to a base class constructor (in the initialization section of the definition of a derived class constructor), then the default (zero-argument) constructor of the base class is called automatically. (If there is no default constructor for the base class, that is an error condition.)

The call to the base class constructor is the first action taken by a derived class constructor. Thus, if class B is derived from class A and class C is derived from class B, then when an object of the class C is created, first a constructor for the class A is called, then a constructor for B is called, and finally the remaining actions of the C constructor are taken.

The protected Qualifier

As you have seen, you cannot access a private member variable or private member function in the definition or implementation of a derived class. There is a classification of member variables and functions that allows them to be accessed by name in a derived class but not anyplace else, such as in some class that is not a derived class. If you use the qualifier protected, rather than private or public, before a member variable or member function of a class, then for any class or function other than a derived class, the effect is the same as if the member variable were labeled private; however, in a derived class the variable can be accessed by name.

For example, consider the class HourlyEmployee that was derived from the base class Employee. We were required to use accessor and mutator member functions to manipulate the inherited member variables in the definition of HourlyEmployee::printCheck. If all the private member variables in the class Employee were labeled with the keyword protected instead of private, the definition of HourlyEmployee::printCheck in the derived class Employee could be simplified to the following:

void HourlyEmployee::printCheck( )
 //Only works if the member variables of Employee are marked
 //protected instead of private.
 {
    netPay = hours * wageRate;

    cout << "\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
    cout << "Pay to the order of " << name << endl;
    cout << "The sum of " << netPay << " Dollars\n";
    cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
    cout << "Check Stub: NOT NEGOTIABLE\n";
    cout << "Employee Number: " << ssn << endl;
    cout << "Hourly Employee. \nHours worked: " << hours
         << " Rate: " << wageRate << " Pay: " << netPay
         << endl;
    cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
 }

In the derived class HourlyEmployee, the inherited member variables name, netPay, and ssn can be accessed by name, provided they are marked as protected (as opposed to private) in the base class Employee. However, in any class that is not derived from the class Employee, these member variables are treated as if they were marked private.

Member variables that are protected in the base class act as though they were also marked protected in any derived class. For example, suppose you define a derived class PartTimeHourlyEmployee of the class HourlyEmployee. The class PartTimeHourlyEmployee inherits all the member variables of the class HourlyEmployee, including the member variables that HourlyEmployee inherits from the class Employee. So, the class PartTimeHourlyEmployee will have the member variables netPay, name, and ssn. If these member variables were marked protected in the class Employee, then they can be used by name in the definitions of functions of the class PartTimeHourlyEmployee. Except for derived classes (and derived classes of derived classes, etc.), a member variable that is marked protected is treated the same as if it were marked private.

We include a discussion of protected member variables primarily because you will see them used and should be familiar with them. Many, but not all, programming authorities say it is bad style to use protected member variables. They say it compromises the principle of hiding the class implementation and that all member variables should be marked private. If all member variables are marked private, the inherited member variables cannot be accessed by name in derived class function definitions. However, this is not as bad as its sounds. The inherited private member variables can be accessed indirectly by invoking inherited functions that either read or change the private inherited variables. Since authorities differ, you will have to make your own decision on whether or not to use protected members.

Self-Test Exercises

  1. Is the following program legal (assuming appropriate #include and using directives are added)?

    void showEmployeeData(const Employee object);
    int main( )
    {
        HourlyEmployee joe("Mighty Joe",
                           "123-45-6789", 20.50, 40);
        SalariedEmployee boss("Mr. Big Shot",
                              "987-65-4321", 10500.50);
        showEmployeeData(joe);
        showEmployeeData(boss);
    return 0;
    }
    void showEmployeeData(const Employee object)
    {
        cout << "Name: " << object.getName( ) << endl;
        cout << "Social Security Number: "
             << object.getSSN( ) << endl;
    }
  2. Give a definition for a class SmartBut that is a derived class of the base class Smart, which we reproduce for you here. Do not bother with #include directives or namespace details.

    class Smart
    {
     public:
        Smart( );
     void printAnswer( ) const;
     protected:
         int a;
         int b;
    };

    This class should have an additional data field, crazy, that is of type bool, one additional member function that takes no arguments and returns a value of type bool, and suitable constructors. The new function is named isCrazy. You do not need to give any implementations, just the class definition.

  3. Is the following a legal definition of the member function isCrazy in the derived class SmartBut discussed in Self-Test Exercise 2? Explain your answer. (Remember, the question asks if it is legal, not if it is a sensible definition.)

    bool SmartBut::isCrazy( ) const
     {
         if (a > b)
           return false;
         else
            return true;
    }
    

Redefinition of Member Functions

In the definition of the derived class HourlyEmployee (Display 15.4), we gave the declarations for the new member functions setRate, getRate, setHours, and getHours. We also gave the function declaration for only one of the member functions inherited from the class Employee. The inherited member functions whose function declarations were not given (such as setName and setSSN) are inherited unchanged. They have the same definition in the class HourlyEmployee as they do in the base class Employee. When you define a derived class like HourlyEmployee, you list only the function declarations for the inherited member functions whose definitions you want to change to have a different definition in the derived class. If you look at the implementation of the class HourlyEmployee, given in Display 15.6, you will see that we have redefined the inherited member function printCheck. The class SalariedEmployee also gives a new definition to the member function printCheck, as shown in Display 15.7. Moreover, the two classes give different definitions from each other. The function printCheck is redefined in the derived classes.

Display 15.7 Implementation for the Derived Class SalariedEmployee

 1    //This is the file salariedemployee.cpp.
 2    //This is the implementation for the class SalariedEmployee.
 3    //The interface for the class SalariedEmployee is in
 4    //the header file salariedemployee.h.
 5    #include <iostream>
 6    #include <string>
 7    #include "salariedemployee.h"
 8    using namespace std;

 9    namespace employeessavitch
10    {
11        SalariedEmployee::SalariedEmployee( ) : Employee( ), salary(0)
12        {
13            //deliberately empty
14        }
15        SalariedEmployee::SalariedEmployee(string theName, string theNumber,
16            double theWeeklySalary)
17                     : Employee(theName, theNumber), salary(theWeeklySalary)
18        {
19            //deliberately empty
20        }

21        double SalariedEmployee::getSalary( ) const
22        {
23            return salary;
24        }

25        void SalariedEmployee::setSalary(double newSalary)
26        {
27            salary = newSalary;
28        }
29        void SalariedEmployee::printCheck( )
30        {
31            setNetPay(salary);
32            cout << "\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
33            cout << "Pay to the order of " << getName( ) << endl;
34            cout << "The sum of " << getNetPay( ) << " Dollars\n";
35            cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
36            cout << "Check Stub NOT NEGOTIABLE \n";

37        void SalariedEmployee::printCheck( )
38        {
39            setNetPay(salary);
40            cout << "\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
41            cout << "Pay to the order of " << getName( ) << endl;
42            cout << "The sum of " << getNetPay( ) << " Dollars\n";
43            cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
44            cout << "Check Stub NOT NEGOTIABLE \n";
45            cout << "Employee Number: " << getSSN( ) << endl;
46            cout << "Salaried Employee. Regular Pay: "
47                 << salary << endl;
48            cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
49        }
50    }//employeessavitch

Display 15.8 gives a demonstration program that illustrates the use of the derived classes HourlyEmployee and SalariedEmployee.

Display 15.8 Using Derived Classes

 1    #include <iostream>
 2    #include "hourlyemployee.h"
 3    #include "salariedemployee.h"
 4    using std::cout;
 5    using std::endl;
 6    using namespace employeessavitch;

 7    int main( )
 8    {
 9        HourlyEmployee joe;
10        joe.setName("Mighty Joe");
11        joe.setSSN("123-45-6789");
12        joe.setRate(20.50);
13        joe.setHours(40);
14        cout << "Check for " << joe.getName( )
15             << " for " << joe.getHours( ) << " hours.\n";
16        joe.printCheck( );
17        cout << endl;

18        SalariedEmployee boss("Mr. Big Shot", "987-65-4321", 10500.50);
19        cout << "Check for " <<boss.getName( )<< endl;
20        boss.printCheck( );
                              The functions setName, setSSN, setRate, setHours, and
                              getName are inherited unchanged from the class Employee. The

21        return 0;        function printCheck is redefined. The function getHours was added
22    }                       to the derived class HourlyEmployee

Sample Dialogue


Check for Mighty Joe for 40 hours.
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Pay to the order of Mighty Joe
The sum of 820 Dollars
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Check Stub: NOT NEGOTIABLE
Employee Number: 123-45-6789
Hourly Employee.
Hours worked: 40 Rate: 20.5 Pay: 820
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Check for Mr. Big Shot
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Pay to the order of Mr. Big Shot
The sum of 10500.5 Dollars
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Check Stub NOT NEGOTIABLE
Employee Number: 987-65-4321
Salaried Employee. Regular Pay: 10500.5
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

Redefining Versus Overloading

Do not confuse redefining a function definition in a derived class with overloading a function name. When you redefine a function definition, the new function definition given in the derived class has the same number and types of parameters. On the other hand, if the function in the derived class were to have a different number of parameters or a parameter of a different type from the function in the base class, then the derived class would have both functions. That would be overloading. For example, suppose we added a function with the following function declaration to the definition of the class HourlyEmployee:

void setName(string firstName, string lastName);

The class HourlyEmployee would have this two-argument function setName, and it would also inherit the following one-argument function setName:

void setName(string newName);

The class HourlyEmployee would have two functions named setName. This would be overloading the function name setName.

On the other hand, both the class Employee and the class HourlyEmployee define a function with the following function declaration:

void printCheck( );

In this case, the class HourlyEmployee has only one function named printCheck, but the definition of the function printCheck for the class HourlyEmployee is different from its definition for the class Employee. In this case, the function printCheck has been redefined.

If you get redefining and overloading confused, you do have one consolation. They are both legal. So, it is more important to learn how to use them than it is to learn to distinguish between them. Nonetheless, you should learn the difference.

Access to a Redefined Base Function

Suppose you redefine a function so that it has a different definition in the derived class from what it had in the base class. The definition that was given in the base class is not completely lost to the derived class objects. However, if you want to invoke the version of the function given in the base class with an object in the derived class, you need some way to say “use the definition of this function as given in the base class (even though I am an object of the derived class).” The way you say this is to use the scope resolution operator with the name of the base class. An example should clarify the details.

Consider the base class Employee (Display 15.2) and the derived class HourlyEmployee (Display 15.4). The function printCheck( ) is defined in both classes. Now suppose you have an object of each class, as in

Employee janeE;
HourlyEmployee sallyH;

Then

janeE.printCheck( );

uses the definition of printCheck given in the class Employee, and

sallyH.printCheck( );

uses the definition of printCheck given in the class HourlyEmployee.

But, suppose you want to invoke the version of printCheck given in the definition of the base class Employee with the derived class object sallyH as the calling object for printCheck. You do that as follows:

sallyH.Employee::printCheck( );

Of course, you are unlikely to want to use the version of printCheck given in the particular class Employee, but with other classes and other functions, you may occasionally want to use a function definition from a base class with a derived class object. An example is given in Self-Test Exercise 6.

Self-Test Exercises

  1. The class SalariedEmployee inherits both of the functions getName and printCheck (among other things) from the base class Employee, yet only the function declaration for the function printCheck is given in the definition of the class SalariedEmployee. Why isn’t the function declaration for the function getName given in the definition of SalariedEmployee?

  2. Give a definition for a class TitledEmployee that is a derived class of the base class SalariedEmployee given in Display 15.5. The class TitledEmployee has one additional member variable of type string called title. It also has two additional member functions: getTitle, which takes no arguments and returns a string; and setTitle, which is a void function that takes one argument of type string. It also redefines the member function setName. You do not need to give any implementations, just the class definition. However, do give all needed #include directives and all using namespace directives. Place the class TitledEmployee in the namespace employeessavitch.

  3. Give the definitions of the constructors for the class TitledEmployee that you gave as the answer to Self-Test Exercise 5. Also, give the redefinition of the member function setName. The function setName should insert the title into the name. Do not bother with #include directives or namespace details.