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.1513.16), HourlyEmployee (Figs. 13.1713.18), CommissionEmployee (Figs. 13.1913.20) and BasePlusCommissionEmployee (Figs. 13.2113.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.