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 run-time 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.
Some compilers, such as Microsoft Visual C++ .NET, 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. To enable RTTI in Visual C++ .NET, select the Project menu and then select the properties option for the current project. In the Property Pages dialog box that appears, select Configuration Properties > C/C++ > Language. Then choose Yes (/GR) from the combo box next to Enable Run-Time Type Info. Finally, click OK to save the settings.
The program in Fig. 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 four-element vector employees that stores pointers to Employee objects. Lines 3441 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).
Figure 13.25. Demonstrating downcasting and run-time type information.
(This item is displayed on pages 732 - 734 in the print version)
1 // Fig. 13.25: fig13_25.cpp 2 // Demonstrating downcasting and run-time type information. 3 // NOTE: For this example to run in Visual C++ .NET, 4 // you need to enable RTTI (Run-Time Type Info) for the project. 5 #include 6 using std::cout; 7 using std::endl; 8 using std::fixed; 9 10 #include 11 using std::setprecision; 12 13 #include 14 using std::vector; 15 16 #include 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() << " "; 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
|
The for statement at lines 4466 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 5052 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 5052 is not 0, the object is the correct type and the if statement (lines 5663) 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 functionanother example of dynamic binding.
The for loop at lines 6976 displays 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. [Note: The exact contents of the string returned by type_info member function name may vary by compiler.] To use typeid, the program must include header file (line 16).
Note that we avoid several compilation errors in this example by downcasting an Employee pointer to a BasePlusCommissionEmployee pointer (lines 5052). If we remove the dynamic_cast from line 51 and attempt to assign the current Employee pointer directly to BasePlusCommissionEmployee pointer commissionPtr, we will 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 applya 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 commissionPtr, 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 Employeeearnings, print and Employee's get and set functions.
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