13.8. Case Study: Payroll System Using
Polymorphism and Runtime Type Information with Downcasting,
dynamic_cast, typeid and type_info
Recall from the problem statement at the beginning of Section
13.6 that, for the current pay period, our
fictitious company has decided to reward BasePlusCommissionEmployees by adding 10 percent to their base salaries. When
processing Employee objects polymorphically in Section
13.6.6, we did not need to worry about the
"specifics." Now, however, to adjust the base salaries of
BasePlusCommissionEmployees, we have to determine the specific type of
each Employee object at execution time,
then act appropriately. This section demonstrates the powerful capabilities of
runtime type information (RTTI) and dynamic casting, which enable a program to
determine the type of an object at execution time and act on that object
accordingly.
[Note:
Some compilers require that RTTI be enabled before it can be used in a program.
Consult your compiler's documentation to determine whether your compiler has
similar requirements. In Visual C++ 2005, this option is enabled by
default.]
Figure
13.25 uses the Employee hierarchy developed in Section
13.6 and increases by 10 percent the base salary of
each BasePlusCommissionEmployee. Line 31 declares fourelement
vector employees that stores pointers to Employee objects. Lines 34–41 populate the vector with the
addresses of dynamically allocated objects of classes SalariedEmployee
(Figs.
13.15–13.16),
HourlyEmployee (Figs.
13.17–13.18),
CommissionEmployee (Figs.
13.19–13.20) and BasePlusCommissionEmployee (Figs.
13.21–13.22).
Fig. 13.25. Demonstrating
downcasting and runtime type information.
1 // Fig. 13.25: fig13_25.cpp
2 // Demonstrating downcasting and runtime type information.
3 // NOTE: You may need to enable RTTI on your compiler
4 // before you can execute this application.
5 #include <iostream>
6 using std::cout;
7 using std::endl;
8 using std::fixed;
9
10 #include <iomanip>
11 using std::setprecision;
12
13 #include <vector>
14 using std::vector;
15
16 #include <typeinfo>
17
18 // include definitions of classes in Employee hierarchy
19 #include "Employee.h"
20 #include "SalariedEmployee.h"
21 #include "HourlyEmployee.h"
22 #include "CommissionEmployee.h"
23 #include "BasePlusCommissionEmployee.h"
24
25 int main()
26 {
27 // set floating-point output formatting
28 cout << fixed << setprecision( 2 );
29
30 // create vector of four base-class pointers
31 vector < Employee * > employees( 4 );
32
33 // initialize vector with various kinds of Employees
34 employees[ 0 ] = new SalariedEmployee(
35 "John", "Smith", "111-11-1111", 800 );
36 employees[ 1 ] = new HourlyEmployee(
37 "Karen", "Price", "222-22-2222", 16.75, 40 );
38 employees[ 2 ] = new CommissionEmployee(
39 "Sue", "Jones", "333-33-3333", 10000, .06 );
40 employees[ 3 ] = new BasePlusCommissionEmployee(
41 "Bob", "Lewis", "444-44-4444", 5000, .04, 300 );
42
43 // polymorphically process each element in vector employees
44 for ( size_t i = 0; i < employees.size(); i++ )
45 {
46 employees[ i ]->print(); // output employee information
47 cout << endl;
48
49 // downcast pointer
50 BasePlusCommissionEmployee *derivedPtr =
51 dynamic_cast < BasePlusCommissionEmployee * >
52 ( employees[ i ] );
53
54 // determine whether element points to base-salaried
55 // commission employee
56 if ( derivedPtr != 0 ) // 0 if not a BasePlusCommissionEmployee
57 {
58 double oldBaseSalary = derivedPtr->getBaseSalary();
59 cout << "old base salary: $" << oldBaseSalary << endl;
60 derivedPtr->setBaseSalary( 1.10 * oldBaseSalary );
61 cout << "new base salary with 10% increase is: $"
62 << derivedPtr->getBaseSalary() << endl;
63 } // end if
64
65 cout << "earned $" << employees[ i ]->earnings() << "\n\n";
66 } // end for
67
68 // release objects pointed to by vector's elements
69 for ( size_t j = 0; j < employees.size(); j++ )
70 {
71 // output class name
72 cout << "deleting object of "
73 << typeid( *employees[ j ] ).name() << endl;
74
75 delete employees[ j ];
76 } // end for
77
78 return 0;
79 } // end main
|
salaried employee: John Smith
social security number: 111-11-1111
weekly salary: 800.00
earned $800.00
hourly employee: Karen Price
social security number: 222-22-2222
hourly wage: 16.75; hours worked: 40.00
earned $670.00
commission employee: Sue Jones
social security number: 333-33-3333
gross sales: 10000.00; commission rate: 0.06
earned $600.00
base-salaried commission employee: Bob Lewis
social security number: 444-44-4444
gross sales: 5000.00; commission rate: 0.04; base salary: 300.00
old base salary: $300.00
new base salary with 10% increase is: $330.00
earned $530.00
deleting object of class SalariedEmployee
deleting object of class HourlyEmployee
deleting object of class CommissionEmployee
deleting object of class BasePlusCommissionEmployee
|
The for statement in lines 44–66 iterates through the
employees vector and displays each Employee's information by invoking member function
print (line 46). Recall that because print is declared
virtual in base class Employee,
the system invokes the appropriate derived-class object's print
function.
In this example, as we encounter
BasePlusCommissionEmployee objects, we wish
to increase their base salary by 10 percent. Since we process the employees
generically (i.e., polymorphically), we cannot (with the techniques we've
learned) be certain as to which type of Employee is being manipulated at any given time. This creates a
problem, because BasePlusCommissionEmployee
employees must be identified when we encounter them so they can receive the 10
percent salary increase. To accomplish this, we use operator dynamic_cast (line 51) to determine
whether the type of each object is BasePlusCommissionEmployee. This is
the downcast operation we referred to in Section
13.3.3. Lines 50–52 dynamically downcast Employees[ i ] from type
Employee * to type BasePlusCommissionEmployee *. If the
vector element points to an object that is
a BasePlusCommissionEmployee object, then that object's address
is assigned to commissionPtr; otherwise, 0 is assigned to
derived-class pointer derivedPtr.
If the value returned by the dynamic_cast operator in lines 50–52 is not 0, the object is the correct
type, and the if statement (lines 56–63)
performs the special processing required for the
BasePlusCommissionEmployee object. Lines 58, 60 and 62 invoke
BasePlusCommissionEmployee functions getBaseSalary and
setBaseSalary to retrieve and update the
employee's salary.
Line 65 invokes member function
earnings on the object to which employees[
i ] points. Recall that earnings is declared virtual in the base class, so the program invokes the derived-class
object's earnings function—another example of dynamic binding.
Lines 69–76 display each employee's object type and uses the
delete operator to deallocate the dynamic memory to which each
vector element points. Operator typeid (line 73) returns a reference to
an object of class type_info that contains the information about the type of its
operand, including the name of that type. When invoked, type_info
member function name (line 73) returns a pointer-based string that contains the
type name (e.g., "class BasePlusCommissionEmployee") of
the argument passed to typeid. To use typeid, the program must
include header file <typeinfo> (line 16).
Portability Tip 13.1
|
The string returned by
type_info member function name may vary by
compiler. |
Note that we avoid several
compilation errors in this example by downcasting an Employee pointer to a BasePlusCommissionEmployee pointer (lines 50–52). If we remove the
dynamic_cast from line 51 and attempt to
assign the current Employee pointer directly to
BasePlusCommissionEmployee pointer derivedPtr, we'll receive a compilation error. C++ does not allow a
program to assign a base-class pointer to a derived-class pointer because the
is-a relationship does not apply—a
CommissionEmployee is not a
BasePlusCommissionEmployee. The is-a relationship applies only between the derived class and its
base classes, not vice versa.
Similarly, if lines 58, 60 and 62 used the
current base-class pointer from employees, rather than derived-class
pointer derivedPtr, to invoke derived-class-only functions
getBaseSalary and setBaseSalary, we would receive a compilation error at each of
these lines. As you learned in Section
13.3.3, attempting to invoke derived-class-only
functions through a base-class pointer is not allowed. Although lines 58, 60 and
62 execute only if commissionPtr is not 0 (i.e.,
if the cast can be performed), we cannot attempt to invoke derived-class
BasePlusCommissionEmployee functions getBaseSalary and
setBaseSalary on the base-class Employee
pointer. Recall that, using a base class Employee pointer, we can invoke only
functions found in base class Employee—earnings, print and
Employee's get and set functions.