This section reexamines the CommissionEmployee-BasePlusCommissionEmployee hierarchy that we explored throughout Section 12.4. 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 four types: Salaried employees are paid a fixed weekly salary regardless of the number of hours worked, hourly employees are paid by the hour and receive overtime pay for all hours worked in excess of 40 hours, 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, CommissionEmployee and HourlyEmployee. Class BasePlusCommissionEmployeederived from CommissionEmployeerepresents the last employee type. The UML class diagram in Fig. 13.11 shows the inheritance hierarchy for our polymorphic employee payroll application. Note that abstract class name Employee is italicized, as per the convention of the UML.
Figure 13.11. Employee hierarchy UML class diagram.
Abstract base class Employee declares the "interface" to the hierarchythat 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.
Software Engineering Observation 13.10
A derived class can inherit interface or implementation from a base class. Hierarchies designed for implementation inheritance tend to have their functionality high in the hierarchyeach new derived class inherits one or more member functions that were defined in 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 hierarchya base class specifies one or more functions that should be defined for each class in the hierarchy (i.e., they have the same prototype), but the individual derived classes provide their own implementations of the function(s). |
The following sections implement the Employee class hierarchy. The first five each implement one of the abstract or concrete classes. The last section implements a test program that builds objects of all these classes and processes the objects polymorphically.
13.6.1. Creating Abstract Base Class Employee
Class Employee (Figs. 13.1313.14, discussed in further detail shortly) provides functions earnings and print, in addition to various get and set functions that manipulate Employee's data members. An earnings function certainly applies generically 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 functionthere is 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. We maintain 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 classbecause of inheritance, however, all objects of all 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. This enables the designer of the class hierarchy to demand that each derived class provide an appropriate pay calculation, if indeed that derived class is to be concrete.
Function print in class Employee displays the first name, last name and social security number of the employee. As we will see, each derived class of Employee overrides function print to output the employee's type (e.g., "salaried employee:") followed by the rest of the employee's information.
The diagram in Fig. 13.12 shows each of the five classes in the hierarchy down the left side and functions earnings and print across the top. For each class, the diagram shows the desired results of each function. Note that class Employee specifies "= 0" for function earnings to indicate that this is a pure virtual function. 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 are not overridden in any of the derived classeseach of these functions is inherited and used "as is" by each of the derived classes.
Figure 13.12. Polymorphic interface for the Employee hierarchy classes.
(This item is displayed on page 713 in the print version)
Let us consider class Employee's header file (Fig. 13.13). The public member functions include a constructor that takes the first name, last name and social security number as arguments (line 12); set functions that set the first name, last name and social security number (lines 14, 17 and 20, respectively); get functions that return the first name, last name and social security number (lines 15, 18 and 21, respectively); pure virtual function earnings (line 24) and virtual function print (line 25).
Figure 13.13. Employee class header file.
(This item is displayed on pages 713 - 714 in the print version)
1 // Fig. 13.13: Employee.h 2 // Employee abstract base class. 3 #ifndef EMPLOYEE_H 4 #define EMPLOYEE_H 5 6 #include // C++ standard string class 7 using std::string; 8 9 class Employee 10 { 11 public: 12 Employee( const string &, const string &, const string & ); 13 14 void setFirstName( const string & ); // set first name 15 string getFirstName() const; // return first name 16 17 void setLastName( const string & ); // set last name 18 string getLastName() const; // return last name 19 20 void setSocialSecurityNumber( const string & ); // set SSN 21 string getSocialSecurityNumber() const; // return SSN 22 23 // pure virtual function makes Employee abstract base class 24 virtual double earnings() const = 0; // pure virtual 25 virtual void print() const; // virtual 26 private: 27 string firstName; 28 string lastName; 29 string socialSecurityNumber; 30 }; // end class Employee 31 32 #endif // EMPLOYEE_H |
Recall that we declared earnings as a pure virtual function because we first must know the specific Employee type to determine the appropriate earnings calculations. Declaring this function as pure virtual indicates that each concrete derived class must provide an appropriate earnings implementation and that a program can use base-class Employee pointers to invoke function earnings polymorphically for any type of Employee.
Figure 13.14 contains the member-function implementations for class Employee. No implementation is provided for virtual function earnings. Note that the Employee constructor (lines 1015) does not validate the social security number. Normally, such validation should be provided. An exercise in Chapter 12 asks you to validate a social security number to ensure that it is in the form ###-##-####, where each # represents a digit.
Figure 13.14. Employee class implementation file.
(This item is displayed on pages 714 - 715 in the print version)
1 // Fig. 13.14: Employee.cpp 2 // Abstract-base-class Employee member-function definitions. 3 // Note: No definitions are given for pure virtual functions. 4 #include 5 using std::cout; 6 7 #include "Employee.h" // Employee class definition 8 9 // constructor 10 Employee::Employee( const string &first, const string &last, 11 const string &ssn ) 12 : firstName( first ), lastName( last ), socialSecurityNumber( ssn ) 13 { 14 // empty body 15 } // end Employee constructor 16 17 // set first name 18 void Employee::setFirstName( const string &first ) 19 { 20 firstName = first; 21 } // end function setFirstName 22 23 // return first name 24 string Employee::getFirstName() const 25 { 26 return firstName; 27 } // end function getFirstName 28 29 // set last name 30 void Employee::setLastName( const string &last ) 31 { 32 lastName = last; 33 } // end function setLastName 34 35 // return last name 36 string Employee::getLastName() const 37 { 38 return lastName; 39 } // end function getLastName 40 41 // set social security number 42 void Employee::setSocialSecurityNumber( const string &ssn ) 43 { 44 socialSecurityNumber = ssn; // should validate 45 } // end function setSocialSecurityNumber 46 47 // return social security number 48 string Employee::getSocialSecurityNumber() const 49 { 50 return socialSecurityNumber; 51 } // end function getSocialSecurityNumber 52 53 // print Employee's information (virtual, but not pure virtual) 54 void Employee::print() const 55 { 56 cout << getFirstName() << ' ' << getLastName() 57 << " social security number: " << getSocialSecurityNumber(); 58 } // end function print |
Note that virtual function print (Fig. 13.14, lines 5458) provides an implementation that will be overridden in each of the derived classes. Each of these functions will, however, use the abstract class's version of print to print information common to all classes in the Employee hierarchy.
13.6.2. Creating Concrete Derived Class SalariedEmployee
Class SalariedEmployee (Figs. 13.1513.16) derives from class Employee (line 8 of Fig. 13.15). 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 1112); a set function to assign a new nonnegative value to data member weeklySalary (lines 14); a get function to return weeklySalary's value (line 15); a virtual function earnings that calculates a SalariedEmployee's earnings (line 18) and a virtual function print that outputs the employee's type, namely, "salaried employee:" followed by employee-specific information produced by base class Employee's print function and SalariedEmployee's getWeeklySalary function (line 19).
Figure 13.15. SalariedEmployee class header file.
(This item is displayed on page 716 in the print version)
1 // Fig. 13.15: SalariedEmployee.h 2 // SalariedEmployee class derived from Employee. 3 #ifndef SALARIED_H 4 #define SALARIED_H 5 6 #include "Employee.h" // Employee class definition 7 8 class SalariedEmployee : public Employee 9 { 10 public: 11 SalariedEmployee( const string &, const string &, 12 const string &, double = 0.0 ); 13 14 void setWeeklySalary( double ); // set weekly salary 15 double getWeeklySalary() const; // return weekly salary 16 17 // keyword virtual signals intent to override 18 virtual double earnings() const; // calculate earnings 19 virtual void print() const; // print SalariedEmployee object 20 private: 21 double weeklySalary; // salary per week 22 }; // end class SalariedEmployee 23 24 #endif // SALARIED_H |
Figure 13.16 contains the member-function implementations for SalariedEmployee. The class's constructor passes the first name, last name and social security number to the Employee constructor (line 11) to initialize the private data members that are inherited from the base class, but not accessible in the derived class. Function earnings (line 3033) overrides pure virtual function earnings in Employee to provide a concrete implementation that returns the SalariedEmployee's weekly salary. If we do not implement earnings, class SalariedEmployee would be an abstract class, and any attempt to instantiate an object of the class would result in a compilation error (and, of course, we want SalariedEmployee here to be a concrete class). Note that in class SalariedEmployee's header file, we declared member functions earnings and print as virtual (lines 1819 of Fig. 13.15)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 throughout the class hierarchy. Recall from Good Programming Practice 13.1 that explicitly declaring such functions virtual at every level of the hierarchy can promote program clarity.
Figure 13.16. SalariedEmployee class implementation file.
(This item is displayed on pages 716 - 717 in the print version)
1 // Fig. 13.16: SalariedEmployee.cpp 2 // SalariedEmployee class member-function definitions. 3 #include 4 using std::cout; 5 6 #include "SalariedEmployee.h" // SalariedEmployee class definition 7 8 // constructor 9 SalariedEmployee::SalariedEmployee( const string &first, 10 const string &last, const string &ssn, double salary ) 11 : Employee( first, last, ssn ) 12 { 13 setWeeklySalary( salary ); 14 } // end SalariedEmployee constructor 15 16 // set salary 17 void SalariedEmployee::setWeeklySalary( double salary ) 18 { 19 weeklySalary = ( salary < 0.0 ) ? 0.0 : salary; 20 } // end function setWeeklySalary 21 22 // return salary 23 double SalariedEmployee::getWeeklySalary() const 24 { 25 return weeklySalary; 26 } // end function getWeeklySalary 27 28 // calculate earnings; 29 // override pure virtual function earnings in Employee 30 double SalariedEmployee::earnings() const 31 { 32 return getWeeklySalary(); 33 } // end function earnings 34 35 // print SalariedEmployee's information 36 void SalariedEmployee::print() const 37 { 38 cout << "salaried employee: "; 39 Employee::print(); // reuse abstract base-class print function 40 cout << " weekly salary: " << getWeeklySalary(); 41 } // end function print |
Function print of class SalariedEmployee (lines 3641 of Fig. 13.16) overrides Employee function print. If class SalariedEmployee did not override print, SalariedEmployee would inherit the Employee version of print. In that case, SalariedEmployee's print function would simply return the employee's full name and social security number, which does not adequately represent a SalariedEmployee. To print a SalariedEmployee's complete information, the derived class's print function outputs "salaried employee:" followed by the base-class Employee-specific information (i.e., first name, last name and social security number) printed by invoking the base class's print using the scope resolution operator (line 39)this is a nice example of code reuse. The output produced by SalariedEmployee's print function contains the employee's weekly salary obtained by invoking the class's getWeeklySalary function.
13.6.3. Creating Concrete Derived Class HourlyEmployee
Class HourlyEmployee (Figs. 13.1713.18) also derives from class Employee (line 8 of Fig. 13.17). The public member functions include a constructor (lines 1112) that takes as arguments a first name, a last name, a social security number, an hourly wage and the number of hours worked; set functions that assign new values to data members wage and hours, respectively (lines 14 and 17); get functions to return the values of wage and hours, respectively (lines 15 and 18); a virtual function earnings that calculates an HourlyEmployee's earnings (line 21) and a virtual function print that outputs the employee's type, namely, "hourly employee:" and employee-specific information (line 22).
Figure 13.17. HourlyEmployee class header file.
1 // Fig. 13.17: HourlyEmployee.h 2 // HourlyEmployee class definition. 3 #ifndef HOURLY_H 4 #define HOURLY_H 5 6 #include "Employee.h" // Employee class definition 7 8 class HourlyEmployee : public Employee 9 { 10 public: 11 HourlyEmployee( const string &, const string &, 12 const string &, double = 0.0, double = 0.0 ); 13 14 void setWage( double ); // set hourly wage 15 double getWage() const; // return hourly wage 16 17 void setHours( double ); // set hours worked 18 double getHours() const; // return hours worked 19 20 // keyword virtual signals intent to override 21 virtual double earnings() const; // calculate earnings 22 virtual void print() const; // print HourlyEmployee object 23 private: 24 double wage; // wage per hour 25 double hours; // hours worked for week 26 }; // end class HourlyEmployee 27 28 #endif // HOURLY_H |
Figure 13.18 contains the member-function implementations for class HourlyEmployee. Lines 1821 and 3034 define set functions that assign new values to data members wage and hours, respectively. Function setWage (lines 1821) ensures that wage is nonnegative, and function setHours (lines 3034) ensures that data member hours is between 0 and 168 (the total number of hours in a week). Class HourlyEmployee's get functions are implemented in lines 2427 and 3740. We do not declare these functions virtual, so classes derived from class HourlyEmployee cannot override them (although derived classes certainly can redefine them). Note that the HourlyEmployee constructor, like the SalariedEmployee constructor, passes the first name, last name and social security number to the base class Employee constructor (line 11) to initialize the inherited private data members declared in the base class. In addition, HourlyEmployee's print function calls base-class function print (line 56) to output the Employee-specific information (i.e., first name, last name and social security number)this is another nice example of code reuse.
Figure 13.18. HourlyEmployee class implementation file.
(This item is displayed on pages 718 - 719 in the print version)
1 // Fig. 13.18: HourlyEmployee.cpp 2 // HourlyEmployee class member-function definitions. 3 #include 4 using std::cout; 5 6 #include "HourlyEmployee.h" // HourlyEmployee class definition 7 8 // constructor 9 HourlyEmployee::HourlyEmployee( const string &first, const string &last, 10 const string &ssn, double hourlyWage, double hoursWorked ) 11 : Employee( first, last, ssn ) 12 { 13 setWage( hourlyWage ); // validate hourly wage 14 setHours( hoursWorked ); // validate hours worked 15 } // end HourlyEmployee constructor 16 17 // set wage 18 void HourlyEmployee::setWage( double hourlyWage ) 19 { 20 wage = ( hourlyWage < 0.0 ? 0.0 : hourlyWage ); 21 } // end function setWage 22 23 // return wage 24 double HourlyEmployee::getWage() const 25 { 26 return wage; 27 } // end function getWage 28 29 // set hours worked 30 void HourlyEmployee::setHours( double hoursWorked ) 31 { 32 hours = ( ( ( hoursWorked >= 0.0 ) && ( hoursWorked <= 168.0 ) ) ? 33 hoursWorked : 0.0 ); 34 } // end function setHours 35 36 // return hours worked 37 double HourlyEmployee::getHours() const 38 { 39 return hours; 40 } // end function getHours 41 42 // calculate earnings; 43 // override pure virtual function earnings in Employee 44 double HourlyEmployee::earnings() const 45 { 46 if ( getHours() <= 40 ) // no overtime 47 return getWage() * getHours(); 48 else 49 return 40 * getWage() + ( ( getHours() - 40 ) * getWage() * 1.5 ); 50 } // end function earnings 51 52 // print HourlyEmployee's information 53 void HourlyEmployee::print() const 54 { 55 cout << "hourly employee: "; 56 Employee::print(); // code reuse 57 cout << " hourly wage: " << getWage() << 58 "; hours worked: " << getHours(); 59 } // end function print |
13.6.4. Creating Concrete Derived Class CommissionEmployee
Class CommissionEmployee (Figs. 13.1913.20) derives from class Employee (line 8 of Fig. 13.19). The member-function implementations (Fig. 13.20) include a constructor (lines 915) that takes a first name, a last name, a social security number, a sales amount and a commission rate; set functions (lines 1821 and 3033) to assign new values to data members commissionRate and grossSales, respectively; get functions (lines 2427 and 3639) that retrieve the values of these data members; function earnings (lines 4346) to calculate a CommissionEmployee's earnings; and function print (lines 4955), which outputs the employee's type, namely, "commission employee:" and employee-specific information. The CommissionEmployee's constructor also passes the first name, last name and social security number to the Employee constructor (line 11) to initialize Employee's private data members. Function print calls base-class function print (line 52) to display the Employee-specific information (i.e., first name, last name and social security number).
Figure 13.19. CommissionEmployee class header file.
(This item is displayed on page 720 in the print version)
1 // Fig. 13.19: CommissionEmployee.h 2 // CommissionEmployee class derived from Employee. 3 #ifndef COMMISSION_H 4 #define COMMISSION_H 5 6 #include "Employee.h" // Employee class definition 7 8 class CommissionEmployee : public Employee 9 { 10 public: 11 CommissionEmployee( const string &, const string &, 12 const string &, double = 0.0, double = 0.0 ); 13 14 void setCommissionRate( double ); // set commission rate 15 double getCommissionRate() const; // return commission rate 16 17 void setGrossSales( double ); // set gross sales amount 18 double getGrossSales() const; // return gross sales amount 19 20 // keyword virtual signals intent to override 21 virtual double earnings() const; // calculate earnings 22 virtual void print() const; // print CommissionEmployee object 23 private: 24 double grossSales; // gross weekly sales 25 double commissionRate; // commission percentage 26 }; // end class CommissionEmployee 27 28 #endif // COMMISSION_H |
Figure 13.20. CommissionEmployee class implementation file.
(This item is displayed on pages 720 - 721 in the print version)
1 // Fig. 13.20: CommissionEmployee.cpp 2 // CommissionEmployee class member-function definitions. 3 #include 4 using std::cout; 5 6 #include "CommissionEmployee.h" // CommissionEmployee class definition 7 8 // constructor 9 CommissionEmployee::CommissionEmployee( const string &first, 10 const string &last, const string &ssn, double sales, double rate ) 11 : Employee( first, last, ssn ) 12 { 13 setGrossSales( sales ); 14 setCommissionRate( rate ); 15 } // end CommissionEmployee constructor 16 17 // set commission rate 18 void CommissionEmployee::setCommissionRate( double rate ) 19 { 20 commissionRate = ( ( rate > 0.0 && rate < 1.0 ) ? rate : 0.0 ); 21 } // end function setCommissionRate 22 23 // return commission rate 24 double CommissionEmployee::getCommissionRate() const 25 { 26 return commissionRate; 27 } // end function getCommissionRate 28 29 // set gross sales amount 30 void CommissionEmployee::setGrossSales( double sales ) 31 { 32 grossSales = ( ( sales < 0.0 ) ? 0.0 : sales ); 33 } // end function setGrossSales 34 35 // return gross sales amount 36 double CommissionEmployee::getGrossSales() const 37 { 38 return grossSales; 39 } // end function getGrossSales 40 41 // calculate earnings; 42 // override pure virtual function earnings in Employee 43 double CommissionEmployee::earnings() const 44 { 45 return getCommissionRate() * getGrossSales(); 46 } // end function earnings 47 48 // print CommissionEmployee's information 49 void CommissionEmployee::print() const 50 { 51 cout << "commission employee: "; 52 Employee::print(); // code reuse 53 cout << " gross sales: " << getGrossSales() 54 << "; commission rate: " << getCommissionRate(); 55 } // end function print |
13.6.5. Creating Indirect Concrete Derived Class BasePlusCommissionEmployee
Class BasePlusCommissionEmployee (Figs. 13.2113.22) directly inherits from class CommissionEmployee (line 8 of Fig. 13.21) and therefore is an indirect derived class of class Employee. Class BasePlusCommissionEmployee's member-function implementations include a constructor (lines 1016 of Fig. 13.22) 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 1922) to assign a new value to data member baseSalary and a get function (lines 2528) to return baseSalary's value. Function earnings (lines 3235) calculates a BasePlusCommissionEmployee's earnings. Note that line 34 in function earnings calls base-class CommissionEmployee's earnings function to calculate the commission-based portion of the employee's earnings. This is a nice example of code reuse. BasePlusCommissionEmployee's print function (lines 3843) outputs "base-salaried", followed by the output of base-class CommissionEmployee's print function (another example of code reuse), then the base salary. The resulting output begins with "base-salaried commission employee:" followed by the rest of the BasePlusCommissionEmployee's information. Recall that CommissionEmployee's print displays the employee's first name, last name and social security number by invoking the print function of its base class (i.e., Employee)yet another example of code reuse. Note that BasePlusCommissionEmployee's print initiates a chain of functions calls that spans all three levels of the Employee hierarchy.
Figure 13.21. BasePlusCommissionEmployee class header file.
(This item is displayed on page 722 in the print version)
1 // Fig. 13.21: BasePlusCommissionEmployee.h 2 // BasePlusCommissionEmployee class derived from Employee. 3 #ifndef BASEPLUS_H 4 #define BASEPLUS_H 5 6 #include "CommissionEmployee.h" // CommissionEmployee class definition 7 8 class BasePlusCommissionEmployee : public CommissionEmployee 9 { 10 public: 11 BasePlusCommissionEmployee( const string &, const string &, 12 const string &, double = 0.0, double = 0.0, double = 0.0 ); 13 14 void setBaseSalary( double ); // set base salary 15 double getBaseSalary() const; // return base salary 16 17 // keyword virtual signals intent to override 18 virtual double earnings() const; // calculate earnings 19 virtual void print() const; // print BasePlusCommissionEmployee object 20 private: 21 double baseSalary; // base salary per week 22 }; // end class BasePlusCommissionEmployee 23 24 #endif // BASEPLUS_H |
Figure 13.22. BasePlusCommissionEmployee class implementation file.
1 // Fig. 13.22: BasePlusCommissionEmployee.cpp 2 // BasePlusCommissionEmployee member-function definitions. 3 #include 4 using std::cout; 5 6 // BasePlusCommissionEmployee class definition 7 #include "BasePlusCommissionEmployee.h" 8 9 // constructor 10 BasePlusCommissionEmployee::BasePlusCommissionEmployee( 11 const string &first, const string &last, const string &ssn, 12 double sales, double rate, double salary ) 13 : CommissionEmployee( first, last, ssn, sales, rate ) 14 { 15 setBaseSalary( salary ); // validate and store base salary 16 } // end BasePlusCommissionEmployee constructor 17 18 // set base salary 19 void BasePlusCommissionEmployee::setBaseSalary( double salary ) 20 { 21 baseSalary = ( ( salary < 0.0 ) ? 0.0 : salary ); 22 } // end function setBaseSalary 23 24 // return base salary 25 double BasePlusCommissionEmployee::getBaseSalary() const 26 { 27 return baseSalary; 28 } // end function getBaseSalary 29 30 // calculate earnings; 31 // override pure virtual function earnings in Employee 32 double BasePlusCommissionEmployee::earnings() const 33 { 34 return getBaseSalary() + CommissionEmployee::earnings(); 35 } // end function earnings 36 37 // print BasePlusCommissionEmployee's information 38 void BasePlusCommissionEmployee::print() const 39 { 40 cout << "base-salaried "; 41 CommissionEmployee::print(); // code reuse 42 cout << "; base salary: " << getBaseSalary(); 43 } // end function print |
13.6.6. Demonstrating Polymorphic Processing
To test our Employee hierarchy, the program in Fig. 13.23 creates an object of each of the four concrete classes SalariedEmployee, HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee. The program manipulates these objects, first with static binding, then polymorphically, using a vector of Employee pointers. Lines 3138 create objects of each of the four concrete Employee derived classes. Lines 4351 output each Employee's information and earnings. Each member-function invocation in lines 4351 is an example of static bindingat 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 print and earnings functions are called.
Figure 13.23. Employee class hierarchy driver program.
(This item is displayed on pages 724 - 727 in the print version)
1 // Fig. 13.23: fig13_23.cpp 2 // Processing Employee derived-class objects individually 3 // and polymorphically using dynamic binding. 4 #include 5 using std::cout; 6 using std::endl; 7 using std::fixed; 8 9 #include 10 using std::setprecision; 11 12 #include 13 using std::vector; 14 15 // include definitions of classes in Employee hierarchy 16 #include "Employee.h" 17 #include "SalariedEmployee.h" 18 #include "HourlyEmployee.h" 19 #include "CommissionEmployee.h" 20 #include "BasePlusCommissionEmployee.h" 21 22 void virtualViaPointer( const Employee * const ); // prototype 23 void virtualViaReference( const Employee & ); // prototype 24 25 int main() 26 { 27 // set floating-point output formatting 28 cout << fixed << setprecision( 2 ); 29 30 // create derived-class objects 31 SalariedEmployee salariedEmployee( 32 "John", "Smith", "111-11-1111", 800 ); 33 HourlyEmployee hourlyEmployee( 34 "Karen", "Price", "222-22-2222", 16.75, 40 ); 35 CommissionEmployee commissionEmployee( 36 "Sue", "Jones", "333-33-3333", 10000, .06 ); 37 BasePlusCommissionEmployee basePlusCommissionEmployee( 38 "Bob", "Lewis", "444-44-4444", 5000, .04, 300 ); 39 40 cout << "Employees processed individually using static binding: "; 41 42 // output each Employee's information and earnings using static binding 43 salariedEmployee.print(); 44 cout << " earned $" << salariedEmployee.earnings() << " "; 45 hourlyEmployee.print(); 46 cout << " earned $" << hourlyEmployee.earnings() << " "; 47 commissionEmployee.print(); 48 cout << " earned $" << commissionEmployee.earnings() << " "; 49 basePlusCommissionEmployee.print(); 50 cout << " earned $" << basePlusCommissionEmployee.earnings() 51 << " "; 52 53 // create vector of four base-class pointers 54 vector < Employee * > employees( 4 ); 55 56 // initialize vector with Employees 57 employees[ 0 ] = &salariedEmployee; 58 employees[ 1 ] = &hourlyEmployee; 59 employees[ 2 ] = &commissionEmployee; 60 employees[ 3 ] = &basePlusCommissionEmployee; 61 62 cout << "Employees processed polymorphically via dynamic binding: "; 63 64 // call virtualViaPointer to print each Employee's information 65 // and earnings using dynamic binding 66 cout << "Virtual function calls made off base-class pointers: "; 67 68 for ( size_t i = 0; i < employees.size(); i++ ) 69 virtualViaPointer( employees[ i ] ); 70 71 // call virtualViaReference to print each Employee's information 72 // and earnings using dynamic binding 73 cout << "Virtual function calls made off base-class references: "; 74 75 for ( size_t i = 0; i < employees.size(); i++ ) 76 virtualViaReference( *employees[ i ] ); // note dereferencing 77 78 return 0; 79 } // end main 80 81 // call Employee virtual functions print and earnings off a 82 // base-class pointer using dynamic binding 83 void virtualViaPointer( const Employee * const baseClassPtr ) 84 { 85 baseClassPtr->print(); 86 cout << " earned $" << baseClassPtr->earnings() << " "; 87 } // end function virtualViaPointer 88 89 // call Employee virtual functions print and earnings off a 90 // base-class reference using dynamic binding 91 void virtualViaReference( const Employee &baseClassRef ) 92 { 93 baseClassRef.print(); ' 94 cout << " earned $" << baseClassRef.earnings() << " "; 95 } // end function virtualViaReference
|
Line 54 allocates vector employees, which contains four Employee pointers. Line 57 aims employees[0] at object salariedEmployee. Line 58 aims employees[1] at object hourlyEmployee. Line 59 aims employees[2] at object commissionEmployee. Line 60 aims employee[3] at object basePlusCommissionEmployee. The compiler allows these assignments, because a SalariedEmployee is an Employee, an HourlyEmployee is an Employee, a CommissionEmployee is an Employee and a BasePlusCommissionEmployee is an Employee. Therefore, we can assign the addresses of SalariedEmployee, HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee objects to base-class Employee pointers (even though Employee is an abstract class).
The for statement at lines 6869 traverses vector employees and invokes function virtualViaPointer (lines 8387) for each element in employees. Function virtualViaPointer receives in parameter baseClassPtr (of type const Employee * const) the address stored in an employees element. Each call to virtualViaPointer uses baseClassPtr to invoke virtual functions print (line 85) and earnings (line 86). Note that function virtualViaPointer does not contain any SalariedEmployee, HourlyEmployee, CommissionEmployee or BasePlusCommissionEmployee type information. The function knows only about base-class type Employee. Therefore, at compile time, the compiler cannot know which concrete class's functions to call through baseClassPtr. Yet at execution time, each virtual-function invocation calls the function on the object to which baseClassPtr points at that time. 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 note that obtaining the earnings of each Employee polymorphically in line 86 produces the same results as obtaining these employees' earnings via static binding in lines 44, 46, 48 and 50. All virtual function calls to print and earnings are resolved at runtime with dynamic binding.
Finally, another for statement (lines 7576) traverses employees and invokes function virtualViaReference (lines 9195) for each element in the vector. Function virtualViaReference receives in its parameter baseClassRef (of type const Employee &) a reference formed by dereferencing the pointer stored in each employees element (line 76). Each call to virtualViaReference invokes virtual functions print (line 93) and earnings (line 94) via reference 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.
Introduction to Computers, the Internet and World Wide Web
Introduction to C++ Programming
Introduction to Classes and Objects
Control Statements: Part 1
Control Statements: Part 2
Functions and an Introduction to Recursion
Arrays and Vectors
Pointers and Pointer-Based Strings
Classes: A Deeper Look, Part 1
Classes: A Deeper Look, Part 2
Operator Overloading; String and Array Objects
Object-Oriented Programming: Inheritance
Object-Oriented Programming: Polymorphism
Templates
Stream Input/Output
Exception Handling
File Processing
Class string and String Stream Processing
Web Programming
Searching and Sorting
Data Structures
Bits, Characters, C-Strings and structs
Standard Template Library (STL)
Other Topics
Appendix A. Operator Precedence and Associativity Chart
Appendix B. ASCII Character Set
Appendix C. Fundamental Types
Appendix D. Number Systems
Appendix E. C Legacy Code Topics
Appendix F. Preprocessor
Appendix G. ATM Case Study Code
Appendix H. UML 2: Additional Diagram Types
Appendix I. C++ Internet and Web Resources
Appendix J. Introduction to XHTML
Appendix K. XHTML Special Characters
Appendix L. Using the Visual Studio .NET Debugger
Appendix M. Using the GNU C++ Debugger
Bibliography