This section reexamines the CommissionEmployee
–BasePlusCommissionEmployee
hierarchy that we explored throughout Section 11.3. In this example, we use an abstract class and polymorphism to perform payroll calculations based on the type of employee. We create an enhanced employee hierarchy to solve the following problem:
A company pays its employees weekly. The employees are of three types: Salaried employees are paid a fixed weekly salary regardless of the number of hours worked, commission employees are paid a percentage of their sales and base-salary-plus-commission employees receive a base salary plus a percentage of their sales. For the current pay period, the company has decided to reward base-salary-plus-commission employees by adding 10 percent to their base salaries. The company wants to implement a C++ program that performs its payroll calculations polymorphically.
We use abstract class Employee
to represent the general concept of an employee. The classes that derive directly from Employee
are SalariedEmployee
and CommissionEmployee
. Class BasePlusCommissionEmployee
derives from CommissionEmployee
and represents the last employee type. The UML class diagram in Fig. 12.7 shows the inheritance hierarchy for our polymorphic employee payroll application. The abstract class name Employee
is italicized, per the convention of the UML.
Abstract base class Employee
declares the “interface” to the hierarchy—that is, the set of member functions that a program can invoke on all Employee
objects. Each employee, regardless of the way his or her earnings are calculated, has a first name, a last name and a
social security number, so private
data members firstName
, lastName
and socialSecurityNumber
appear in abstract base class Employee
.
A derived class can inherit interface and/or implementation from a base class. Hierarchies designed for implementation inheritance tend to have their functionality high in the hierarchy—each derived class inherits one or more member functions from a base class, and the derived class uses the base-class definitions. Hierarchies designed for interface inheritance tend to have their functionality lower in the hierarchy—a base class specifies one or more functions that should be defined by every derived class, but the individual derived classes provide their own implementations of the function(s).
The following sections implement the Employee
class hierarchy. The first four sections each implement one of the abstract or concrete classes. The last section implements a test program that builds objects of the concrete classes and processes the objects polymorphically.
Employee
Class Employee
(Figs. 12.9–12.10, discussed in further detail shortly) provides functions earnings
and toString
, in addition to various get and set functions that manipulate Employee
’s data members. An earnings
function certainly applies generally to all employees, but each earnings calculation depends on the employee’s class. So we declare earnings
as pure virtual
in base class Employee
because a default implementation does not make sense for that function—there’s not enough information to determine what amount earnings
should return.
Each derived class overrides earnings
with an appropriate implementation. To calculate an employee’s earnings, the program assigns the address of an employee’s object to a base-class Employee
pointer, then invokes the earnings
function on that object.
The test program maintains a vector
of Employee
pointers, each of which points to an Employee
object. Of course, there cannot be Employee
objects, because Employee
is an abstract class—with inheritance, however, all objects of all concrete derived classes of Employee
may nevertheless be thought of as Employee
objects. The program iterates through the vector
and calls function earnings
for each Employee
object. C++ processes these function calls polymorphically. Including earnings
as a pure virtual
function in Employee
forces every direct derived class of Employee
that wishes to be a concrete class to override earnings
.
Function toString
in class Employee
returns a string
containing the first name, last name and social security number of the employee. As we’ll see, each derived class of Employee
overrides function toString
to output the employee’s type (e.g., "salaried employee:"
) followed by the rest of the employee’s information. Each each derived class’s toString
could also call earnings
, even though earnings
is a pure-virtual
function in base class Employee
, because each concrete class is guaranteed to have an implementation of earnings
. For this reason, even class Employee
’s toString
function can call earnings
— at runtime, when you call toString
through an Employee
pointer or reference, you’re always calling it on an object of a concrete derived-class.
The diagram in Fig. 12.8 shows each of the four classes in the hierarchy down the left side and functions earnings
and toString
across the top. For each class, the diagram shows the desired results of each function. Italic text represents where the values from a particular object are used in the earnings
and toString
functions. Class Employee
specifies “=0
” for function earnings
to indicate that it’s a pure virtual
function and hence has no implementation. Each derived class overrides this function to provide an appropriate implementation. We do not list base class Employee
’s get and set functions because they’re not overridden in any of the derived classes—each of these functions is inherited and used “as is” by each of the derived classes.
Let’s consider class Employee’s
header (Fig. 12.9). The public
member functions include a constructor that takes the first name, last name and social security number as arguments (line 10); a C++11 default virtual
destructor (line 11) that the compiler generates; set functions that set the first name, last name and social security number (lines 13, 16 and 19, respectively); get functions that return the first name, last name and social security number (lines 14, 17 and 20, respectively); pure virtual
function earnings
(line 23) and virtual
function toString
(line 24).
Recall that we declared earnings
as a pure virtual
function because first we must know the specific Employee
type to determine the appropriate earnings
calculation. Declaring this function as pure virtual
indicates that each concrete derived class must provide an earnings
implementation and that a program can use base-class Employee
pointers (or references) to invoke function earnings
polymorphically for any type of Employee
.
Employee Class Member-Function Definitions
Figure 12.10 contains the member-function definitions for class Employee
. No implementation is provided for virtual
function earnings
. The Employee
constructor (lines 9–11) does not validate the social security number. Normally, such validation should be provided.
The virtual
function toString
(lines 36–39) provides an implementation that will be overridden in each of the derived classes. Each of these functions will, however, use the Employee
class’s version of toString
to get a string
containing the information common to all classes in the Employee
hierarchy.
SalariedEmployee
Class SalariedEmployee
(Figs. 12.11–12.12) derives from class Employee
(line 9 of Fig. 12.11). The public
member functions include a constructor that takes a first name, a last name, a social security number and a weekly salary as arguments (lines 11–12); a C++11 default
virtual
destructor (line 13); a set function to assign a new nonnegative value to data member weeklySalary
(line 15); a get function to return weeklySalary
’s value (line 16); a virtual
function earnings
that calculates a SalariedEmployee
’s earnings (line 19) and a virtual
function toString
(line 20) that outputs the employee’s type, namely, "salaried
employee: "
followed by employee-specific information produced by base class Employee
’s toString
function and SalariedEmployee
’s getWeeklySalary
function.
SalariedEmployee
Class Member-Function DefinitionsFigure 12.12 contains the member-function definitions for SalariedEmployee
. The class’s constructor passes the first name, last name and social security number to the Employee
constructor (line 12) to initialize the private
data members that are inherited from the base class, but not directly accessible in the derived class. Function earnings
(line 30) overrides pure virtual
function earnings
in Employee
to provide a concrete implementation that returns the SalariedEmployee
’s weekly salary. If we did not define earnings
, class SalariedEmployee
would be an abstract class, and attempting to instantiate a SalariedEmployee
object would cause a compilation error. In class SalariedEmployee
’s header, we declared member functions earnings
and toString
as virtual
(lines 19–20 of Fig. 12.11)—actually, placing the virtual
keyword before these member functions is redundant. We defined them as virtual
in base class Employee
, so they remain virtual
functions all the way down the class hierarchy. Explicitly declaring such functions virtual
at every level of the hierarchy promotes program clarity. Not declaring earnings
as pure virtual
signals our intent to provide an implementation in this concrete class.
Function toString
of class SalariedEmployee
(lines 33–40 of Fig. 12.12) overrides Employee
function toString
. If class SalariedEmployee
did not override toString
, SalariedEmployee
would inherit the Employee
version of toString
. In that case, SalariedEmployee
’s toString
function would simply return the employee’s full name and social security number, which does not adequately represent a SalariedEmployee
. To create a string
representation of a SalariedEmployee
’s complete information, the derived class’s toString
function returns "salaried employee: "
followed by the base-class Employee
-specific information (i.e., first name, last name and social security number) returned by invoking the base class’s toString
function using the scope resolution operator (line 37). Without Employee::
, the toString
call would cause infinite recursion. The string
produced by SalariedEmployee
’s toString
function also contains the employee’s weekly salary obtained by invoking the class’s getWeeklySalary
function.
CommissionEmployee
The CommissionEmployee
class (Figs. 12.13–12.14) derives from Employee
(Fig. 12.13, line 9). The member-function implementations in Fig. 12.14 include a constructor (lines 10–15) that takes a first name, last name, social security number, sales amount and commission rate; set functions (lines 18–24 and 30–36) to assign new values to data members grossSales
and commissionRate
, respectively; get functions (lines 27 and 39–41) that retrieve their values; function earnings
(lines 44–46) to calculate a CommissionEmployee
’s earnings; and function toString
(lines 49–56) to output the employee’s type, namely, "commission employee: "
and employee-specific information. The constructor passes the first name, last name and social security number to the Employee
constructor (line 12) to initialize Employee
’s private
data members. Function toString
calls base-class function toString
(line 52) to get a string
representation of the Employee
-specific information.
BasePlusCommissionEmployee
Class BasePlusCommissionEmployee
(Figs. 12.15–12.16) directly inherits from class CommissionEmployee
(line 9 of Fig. 12.15) and therefore is an indirect derived class of class Employee
. Class BasePlusCommissionEmployee
’s member-function implementations in Fig. 12.16 include a constructor (lines 10–15) that takes as arguments a first name, a last name, a social security number, a sales amount, a commission rate and a base salary. It then passes the first name, last name, social security number, sales amount and commission rate to the CommissionEmployee
constructor (line 13) to initialize the inherited members. BasePlusCommissionEmployee
also contains a set function (lines 18–24) to assign a new value to data member baseSalary
and a get function (lines 27–29) to return baseSalary
’s value. Function earnings
(lines 33–35) calculates a BasePlusCommissionEmployee
’s earnings. Line 34 in function earnings
calls base class CommissionEmployee
’s earnings
function to calculate the commission-based portion of the employee’s earnings. BasePlusCommissionEmployee
’s toString
function (lines 38–44) returns "base-salaried"
, followed by the result of base-class CommissionEmployee
’s toString
function, then the base salary. The resulting string
begins with "base-salaried commission employee: "
followed by the rest of the BasePlusCommissionEmployee
’s information. Recall that CommissionEmployee
’s toString
gets a string
containing the employee’s first name, last name and social security number by invoking the toString
function of its base class (i.e., Employee
). BasePlusCommissionEmployee
’s toString
initiates a chain of functions calls that spans all three levels of the Employee
hierarchy.
To test our Employee
hierarchy, the program in Fig. 12.17 creates an object of each of the three concrete classes SalariedEmployee
, CommissionEmployee
and BasePlusCommissionEmployee
. The program manipulates these objects, first with static binding, then polymorphically, using a vector
of Employee
base-class pointers. Lines 20–25 create objects of each of the three concrete Employee
derived classes. Lines 28–34 output each Employee
’s information and earnings. Each member-function invocation in lines 29–34 is an example of static binding—at compile time, because we are using name handles (not pointers or references that could be set at execution time), the compiler can identify each object’s type to determine which toString
and earnings
functions are called.
Lines 37–38 create and initialize the vector
employees
, which contains three Employee
pointers that are aimed at the objects salariedEmployee
, commissionEmployee
and BasePlusCommissionEmployee
, respectively. The compiler allows the elements to be initialized with the addresses of these objects, because a SalariedEmployee
is an Employee
, a CommissionEmployee
is an Employee
and a BasePlusCommissionEmployee
is an Employee
. So, we can assign the addresses of SalariedEmployee
, CommissionEmployee
and BasePlusCommissionEmployee
objects to base-class Employee
pointers, even though Employee
is an abstract class.
Lines 46–48 traverse vector
employees
and invoke function virtualViaPointer
(lines 61–64) for each element in employees
. Function virtualViaPointer
receives in parameter baseClassPtr
the address stored in an employees
element. Each call to virtualViaPointer
uses baseClassPtr
to invoke virtual
functions toString
(line 62) and earnings
(line 63). Function virtualViaPointer
does not contain any SalariedEmployee
, CommissionEmployee
or BasePlusCommissionEmployee
type information. The function knows only about base-class type Employee
. Therefore, the compiler cannot know which concrete class’s functions to call through baseClassPtr
. Yet at execution time, each virtual-function invocation correctly calls the function on the object to which baseClassPtr
currently points. The output illustrates that the appropriate functions for each class are indeed invoked and that each object’s proper information is displayed. For instance, the weekly salary is displayed for the SalariedEmployee
, and the gross sales are displayed for the CommissionEmployee
and BasePlusCommissionEmployee
. Also, obtaining the earnings of each Employee
polymorphically in line 63 produces the same results as obtaining these employees’ earnings via static binding in lines 30, 32 and 34. All virtual
function calls to toString
and earnings
are resolved at runtime with dynamic binding.
Lines 54–56 traverse employees
and invoke function virtualViaReference
(lines 68–71) for each vector
element. Function virtualViaReference
receives in its parameter baseClassRef
(of type const
Employee&
) a reference to the object obtained by dereferencing the pointer stored in each employees
element (line 55). Each call to virtualViaReference
invokes virtual
functions toString
(line 69) and earnings
(line 70) via baseClassRef
to demonstrate that polymorphic processing occurs with base-class references as well. Each virtual
function invocation calls the function on the object to which baseClassRef
refers at runtime. This is another example of dynamic binding. The output produced using base-class references is identical to the output produced using base-class pointers and via static binding earlier in the program.