13. Object-Oriented Programming: Polymorphism

Objectives

In this chapter you’ll learn:

Image  What polymorphism is, how it makes programming more convenient and how it makes systems more extensible and maintainable.

Image  To declare and use virtual functions to effect polymorphism.

Image  The distinction between abstract and concrete classes.

Image  To declare pure virtual functions to create abstract classes.

Image  How to use runtime type information (RTTI) with downcasting, dynamic_cast, typeid and type_info.

Image  How C++ implements virtual functions and dynamic binding “under the hood.”

Image  How to use virtual destructors to ensure that all appropriate destructors run on an object.

One Ring to rule them all, One Ring to find them, One Ring to bring them all and in the darkness bind them.

John Ronald Reuel Tolkien

The silence often of pure innocence
Persuades when speaking fails.

William Shakespeare

General propositions do not decide concrete cases.

Oliver Wendell Holmes

A philosopher of imposing stature doesn’t think in a vacuum. Even his most abstract ideas are, to some extent, conditioned by what is or is not known in the time when he lives.

Alfred North Whitehead

Outline

13.1 Introduction

In Chapters 912, we discussed key object-oriented programming technologies including classes, objects, encapsulation, operator overloading and inheritance. We now continue our study of OOP by explaining and demonstrating polymorphism with inheritance hierarchies. Polymorphism enables us to “program in the general” rather than “program in the specific.” In particular, polymorphism enables us to write programs that process objects of classes that are part of the same class hierarchy as if they were all objects of the hierarchy’s base class. As we’ll soon see, polymorphism works off base-class pointer handles and base-class reference handles, but not off name handles.

Consider the following example of polymorphism. Suppose we create a program that simulates the movement of several types of animals for a biological study. Classes Fish, Frog and Bird represent the three types of animals under investigation. Imagine that each of these classes inherits from base class Animal, which contains a function move and maintains an animal’s current location. Each derived class implements function move. Our program maintains a vector of pointers to objects of the various Animal derived classes. To simulate the animals’ movements, the program sends each object the same message once per second—namely, move. However, each specific type of Animal responds to a move message in its own unique way—a Fish might swim two feet, a Frog might jump three feet and a Bird might fly ten feet. The program issues the same message (i.e., move) to each animal object generically, but each object knows how to modify its location appropriately for its specific type of movement. Relying on each object to know how to “do the right thing” (i.e., do what is appropriate for that type of object) in response to the same function call is the key concept of polymorphism. The same message (in this case, move) sent to a variety of objects has “many forms” of results—hence the term polymorphism.

With polymorphism, we can design and implement systems that are easily extensible—new classes can be added with little or no modification to the general portions of the program, as long as the new classes are part of the inheritance hierarchy that the program processes generically. The only parts of a program that must be altered to accommodate new classes are those that require direct knowledge of the new classes that you add to the hierarchy. For example, if we create class Tortoise that inherits from class Animal (which might respond to a move message by crawling one inch), we need to write only the Tortoise class and the part of the simulation that instantiates a Tortoise object. The portions of the simulation that process each Animal generically can remain the same.

We begin with a sequence of small, focused examples that lead up to an understanding of virtual functions and dynamic binding—polymorphism’s two underlying technologies. We then present a case study that revisits Chapter 12’s Employee hierarchy. In the case study, we define a common “interface” (i.e., set of functionality) for all the classes in the hierarchy. This common functionality among employees is defined in a so-called abstract base class, Employee, from which classes SalariedEmployee, HourlyEmployee and CommissionEmployee inherit directly and class BaseCommissionEmployee inherits indirectly. We’ll soon see what makes a class “abstract” or its opposite—“concrete.”

In this hierarchy, every employee has an earnings function to calculate the employee’s weekly pay. These earnings functions vary by employee type—for instance, SalariedEmployees are paid a fixed weekly salary regardless of the number of hours worked, while HourlyEmployees are paid by the hour and receive overtime pay. We show how to process each employee “in the general”—that is, using base-class pointers to call the earnings function of several derived-class objects. This way, you need to be concerned with only one type of function call, which can be used to execute several different functions based on the objects referred to by the base-class pointers.

A key feature of this chapter is its (optional) detailed discussion of polymorphism, virtual functions and dynamic binding “under the hood,” which uses a detailed diagram to explain how polymorphism can be implemented in C++.

Occasionally, when performing polymorphic processing, we need to program “in the specific,” meaning that operations need to be performed on a specific type of object in a hierarchy—the operation cannot be generally applied to several types of objects. We reuse our Employee hierarchy to demonstrate 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. We use these capabilities to determine whether a particular employee object is a BasePlusCommissionEmployee, then give that employee a 10 percent bonus on his or her base salary.

13.2 Polymorphism Examples

In this section, we discuss several polymorphism examples. With polymorphism, one function can cause different actions to occur, depending on the type of the object on which the function is invoked. This gives you tremendous expressive capability. If class Rectangle is derived from class Quadrilateral, then a Rectangle object is a more specific version of a Quadrilateral object. Therefore, any operation (such as calculating the perimeter or the area) that can be performed on an object of class Quadrilateral also can be performed on an object of class Rectangle. Such operations also can be performed on other kinds of Quadrilaterals, such as Squares, Parallelograms and Trapezoids. The polymorphism occurs when a program invokes a virtual function through a base-class (i.e., Quadrilateral) pointer or reference—C++ dynamically (i.e., at execution time) chooses the correct function for the class from which the object was instantiated. You’ll see a code example that illustrates this process in Section 13.3.

As another example, suppose that we design a video game that manipulates objects of many different types, including objects of classes Martian, Venutian, Plutonian, SpaceShip and LaserBeam. Imagine that each of these classes inherits from the common base class SpaceObject, which contains member function draw. Each derived class implements this function in a manner appropriate for that class. A screen-manager program maintains a container (e.g., a vector) that holds SpaceObject pointers to objects of the various classes. To refresh the screen, the screen manager periodically sends each object the same message—namely, draw. Each type of object responds in a unique way. For example, a Martian object might draw itself in red with the appropriate number of antennae. A SpaceShip object might draw itself as a silver flying saucer. A LaserBeam object might draw itself as a bright red beam across the screen. Again, the same message (in this case, draw) sent to a variety of objects has “many forms” of results.

A polymorphic screen manager facilitates adding new classes to a system with minimal modifications to its code. Suppose that we want to add objects of class Mercurian to our video game. To do so, we must build a class Mercurian that inherits from SpaceObject, but provides its own definition of member function draw. Then, when pointers to objects of class Mercurian appear in the container, you do not need to modify the code for the screen manager. The screen manager invokes member function draw on every object in the container, regardless of the object’s type, so the new Mercurian objects simply “plug right in.” Thus, without modifying the system (other than to build and include the classes themselves), programmers can use polymorphism to accommodate additional classes, including ones that were not even envisioned when the system was created.

Software Engineering Observation 13.1

Software Engineering Observation 13.1

With virtual functions and polymorphism, you can deal in generalities and let the execution-time environment concern itself with the specifics. You can direct a variety of objects to behave in manners appropriate to those objects without even knowing their types (as long as those objects belong to the same inheritance hierarchy and are being accessed off a common base-class pointer).

Software Engineering Observation 13.2

Software Engineering Observation 13.2

Polymorphism promotes extensibility: Software written to invoke polymorphic behavior is written independently of the types of the objects to which messages are sent. Thus, new types of objects that can respond to existing messages can be incorporated into such a system without modifying the base system. Only client code that instantiates new objects must be modified to accommodate new types.

13.3 Relationships Among Objects in an Inheritance Hierarchy

Section 12.4 created an employee class hierarchy, in which class BasePlusCommissionEmployee inherited from class CommissionEmployee. The Chapter 12 examples manipulated CommissionEmployee and BasePlusCommissionEmployee objects by using the objects’ names to invoke their member functions. We now examine the relationships among classes in a hierarchy more closely. The next several sections present a series of examples that demonstrate how base-class and derived-class pointers can be aimed at base-class and derived-class objects, and how those pointers can be used to invoke member functions that manipulate those objects. In Section 13.3.4, we demonstrate how to get polymorphic behavior from base-class pointers aimed at derived-class objects.

In Section 13.3.1, we assign the address of a derived-class object to a base-class pointer, then show that invoking a function via the base-class pointer invokes the base-class functionality—i.e., the type of the handle determines which function is called. In Section 13.3.2, we assign the address of a base-class object to a derived-class pointer, which results in a compilation error. We discuss the error message and investigate why the compiler does not allow such an assignment. In Section 13.3.3, we assign the address of a derived-class object to a base-class pointer, then examine how the base-class pointer can be used to invoke only the base-class functionality—when we attempt to invoke derived-class member functions through the base-class pointer, compilation errors occur. Finally, in Section 13.3.4, we introduce virtual functions and polymorphism by declaring a base-class function as virtual. We then assign the address of a derived-class object to the base-class pointer and use that pointer to invoke derived-class functionality—precisely the capability we need to achieve polymorphic behavior.

A key concept in these examples is to demonstrate that an object of a derived class can be treated as an object of its base class. This enables various interesting manipulations. For example, a program can create an array of base-class pointers that point to objects of many derived-class types. Despite the fact that the derived-class objects are of different types, the compiler allows this because each derived-class object is an object of its base class. However, we cannot treat a base-class object as an object of any of its derived classes. For example, a CommissionEmployee is not a BasePlusCommissionEmployee in the hierarchy defined in Chapter 12—a CommissionEmployee does not have a baseSalary data member and does not have member functions setBaseSalary and getBaseSalary. The is-a relationship applies only from a derived class to its direct and indirect base classes.

13.3.1 Invoking Base-Class Functions from Derived-Class Objects

The example in Figs. 13.113.5 demonstrates three ways to aim base-class pointers and derived-class pointers at base-class objects and derived-class objects. The first two are straightforward—we aim a base-class pointer at a base-class object (and invoke base-class functionality), and we aim a derived-class pointer at a derived-class object (and invoke derived-class functionality). Then, we demonstrate the relationship between derived classes and base classes (i.e., the is-a relationship of inheritance) by aiming a base-class pointer at a derived-class object (and showing that the base-class functionality is indeed available in the derived-class object).

Class CommissionEmployee (Figs. 13.113.2), which we discussed in Chapter 12, is used to represent employees who are paid a percentage of their sales. Class BasePlusCommissionEmployee (Figs. 13.313.4), which we also discussed in Chapter 12, is used to represent employees who receive a base salary plus a percentage of their sales. Each BasePlusCommissionEmployee object is a CommissionEmployee that also has a base salary. Class BasePlusCommissionEmployee’s earnings member function (lines 32–35 of Fig. 13.4) redefines class CommissionEmployee’s earnings member function (lines 79–82 of Fig. 13.2) to include the object’s base salary. Class BasePlusCommissionEmployee’s print member function (lines 38–46 of Fig. 13.4) redefines class CommissionEmployee’s print member function (lines 85–92 of Fig. 13.2) to display the same information as the print function in class CommissionEmployee, as well as the employee’s base salary.

Fig. 13.1 CommissionEmployee class header file.

 1   // Fig. 13.1: CommissionEmployee.h
 2   // CommissionEmployee class definition represents a commission employee.
 3   #ifndef COMMISSION_H
 4   #define COMMISSION_H
 5
 6   #include <string> // C++ standard string class
 7   using std::string;
 8
 9   class CommissionEmployee
10   {
11   public:
12      CommissionEmployee( const string &, const string &, const string &,
13         double = 0.0double = 0.0 );
14
15      void setFirstName( const string & ); // set first name
16      string getFirstName() const// return first name
17
18      void setLastName( const string & ); // set last name
19      string getLastName() const// return last name
20
21      void setSocialSecurityNumber( const string & ); // set SSN
22      string getSocialSecurityNumber() const// return SSN
23
24      void setGrossSales( double ); // set gross sales amount
25      double getGrossSales() const// return gross sales amount
26
27      void setCommissionRate( double ); // set commission rate
28      double getCommissionRate() const// return commission rate
29
30      double earnings() const// calculate earnings
31      void print() const// print CommissionEmployee object
32   private:
33      string firstName;
34      string lastName;
35      string socialSecurityNumber;
36      double grossSales; // gross weekly sales
37      double commissionRate; // commission percentage
38   }; // end class CommissionEmployee
39
40   #endif

Fig. 13.2 CommissionEmployee class implementation file.

 1   // Fig. 13.2: CommissionEmployee.cpp
 2   // Class CommissionEmployee member-function definitions.
 3   #include <iostream>
 4   using std::cout;
 5
 6   #include "CommissionEmployee.h" // CommissionEmployee class definition
 7
 8   // constructor
 9   CommissionEmployee::CommissionEmployee(
10      const string &first, const string &last, const string &ssn,
11      double sales, double rate )
12      : firstName( first ), lastName( last ), socialSecurityNumber( ssn )
13   {
14      setGrossSales( sales ); // validate and store gross sales
15      setCommissionRate( rate ); // validate and store commission rate
16   } // end CommissionEmployee constructor
17
18   // set first name
19   void CommissionEmployee::setFirstName( const string &first )
20   {
21      firstName = first; // should validate
22   } // end function setFirstName
23
24   // return first name
25   string CommissionEmployee::getFirstName() const
26   {
27      return firstName;
28   } // end function getFirstName
29
30   // set last name
31   void CommissionEmployee::setLastName( const string &last )
32   {
33      lastName = last;  // should validate
34   } // end function setLastName
35
36   // return last name
37   string CommissionEmployee::getLastName() const
38   {
39      return lastName;
40   } // end function getLastName
41
42   // set social security number
43   void CommissionEmployee::setSocialSecurityNumber( const string &ssn )
44   {
45      socialSecurityNumber = ssn; // should validate
46   } // end function setSocialSecurityNumber
47
48   // return social security number
49   string CommissionEmployee::getSocialSecurityNumber() const
50   {
51      return socialSecurityNumber;
52   } // end function getSocialSecurityNumber
53

54   // set gross sales amount
55   void CommissionEmployee::setGrossSales( double sales )
56   {
57      grossSales = ( sales < 0.0 ) ? 0.0 : sales;
58   } // end function setGrossSales
59
60   // return gross sales amount
61   double CommissionEmployee::getGrossSales() const
62   {
63      return grossSales;
64   } // end function getGrossSales
65
66   // set commission rate
67   void CommissionEmployee::setCommissionRate( double rate )
68   {
69      commissionRate = ( rate > 0.0 && rate < 1.0 ) ? rate : 0.0;
70   } // end function setCommissionRate
71
72   // return commission rate
73   double CommissionEmployee::getCommissionRate() const
74   {
75      return commissionRate;
76   } // end function getCommissionRate
77
78   // calculate earnings
79   double CommissionEmployee::earnings() const
80   {
81      return getCommissionRate() * getGrossSales();
82   } // end function earnings
83
84   // print CommissionEmployee object                                
85   void CommissionEmployee::print() const                            
86   {                                                                 
87      cout <<"commission employee: "                                 
88         << getFirstName() << ' ' << getLastName()                   
89         << " social security number: " << getSocialSecurityNumber()
90         << " gross sales: " << getGrossSales()                     
91         << " commission rate: " << getCommissionRate();            
92   // end function print                                           


Fig. 13.3 BasePlusCommissionEmployee class header file.

 1   // Fig. 13.3: BasePlusCommissionEmployee.h
 2   // BasePlusCommissionEmployee class derived from class
 3   // CommissionEmployee.
 4   #ifndef BASEPLUS_H
 5   #define BASEPLUS_H
 6
 7   #include <string> // C++ standard string class
 8   using std::string;
 9

10   #include "CommissionEmployee.h" // CommissionEmployee class declaration
11
12   class BasePlusCommissionEmployee : public CommissionEmployee
13   {
14   public:
15      BasePlusCommissionEmployee( const string &, const string &,
16         const string &, double = 0.0double = 0.0double = 0.0 );
17
18      void setBaseSalary( double ); // set base salary
19      double getBaseSalary() const// return base salary
20
21      double earnings() const// calculate earnings
22      void print() const// print BasePlusCommissionEmployee object
23   private:
24      double baseSalary; // base salary
25   }; // end class BasePlusCommissionEmployee
26
27   #endif

Fig. 13.4 BasePlusCommissionEmployee class implementation file.

 1   // Fig. 13.4: BasePlusCommissionEmployee.cpp
 2   // Class BasePlusCommissionEmployee member-function definitions.
 3   #include <iostream>
 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      // explicitly call base-class constructor
14      : CommissionEmployee( first, last, ssn, sales, rate )
15   {
16      setBaseSalary( salary ); // validate and store base salary
17   } // end BasePlusCommissionEmployee constructor
18
19   // set base salary
20   void BasePlusCommissionEmployee::setBaseSalary( double salary )
21   {
22      baseSalary = ( salary < 0.0 ) ? 0.0 : salary;
23   } // end function setBaseSalary
24
25   // return base salary
26   double BasePlusCommissionEmployee::getBaseSalary() const
27   {
28      return baseSalary;
29   } // end function getBaseSalary
30

31   // calculate earnings
32   double BasePlusCommissionEmployee::earnings() const
33   {
34      return getBaseSalary() + CommissionEmployee::earnings();
35   } // end function earnings
36
37   // print BasePlusCommissionEmployee object      
38   void BasePlusCommissionEmployee::print() const  
39   {                                               
40      cout << "base-salaried ";                    
41                                                   
42      // invoke CommissionEmployee's print function
43      CommissionEmployee::print();                 
44                                                   
45      cout << " base salary: " << getBaseSalary();
46   // end function print                         


In Fig. 13.5, lines 19–20 create a CommissionEmployee object and line 23 creates a pointer to a CommissionEmployee object; lines 26–27 create a BasePlusCommissionEmployee object and line 30 creates a pointer to a BasePlusCommissionEmployee object. Lines 37 and 39 use each object’s name (commissionEmployee and basePlusCommissionEmployee, respectively) to invoke each object’s print member function. Line 42 assigns the address of base-class object commissionEmployee to base-class pointer commissionEmployeePtr, which line 45 uses to invoke member function print on that CommissionEmployee object. This invokes the version of print defined in base class CommissionEmployee. Similarly, line 48 assigns the address of derived-class object basePlusCommissionEmployee to derived-class pointer basePlusCommissionEmployeePtr, which line 52 uses to invoke member function print on that BasePlusCommissionEmployee object. This invokes the version of print defined in derived class BasePlusCommissionEmployee. Line 55 then assigns the address of derived-class object basePlusCommissionEmployee to base-class pointer commissionEmployeePtr, which line 59 uses to invoke member function print. This “crossover” is allowed because an object of a derived class is an object of its base class. Note that despite the fact that the base class CommissionEmployee pointer points to a derived class BasePlusCommissionEmployee object, the base class CommissionEmployee’s print member function is invoked (rather than BasePlusCommissionEmployee’s print function). The output of each print member-function invocation in this program reveals that the invoked functionality depends on the type of the handle (i.e., the pointer or reference type) used to invoke the function, not the type of the object to which the handle points. In Section 13.3.4, when we introduce virtual functions, we demonstrate that it is possible to invoke the object type’s functionality, rather than invoke the handle type’s functionality. We’ll see that this is crucial to implementing polymorphic behavior—the key topic of this chapter.

Fig. 13.5 Assigning addresses of base-class and derived-class objects to base-class and derived-class pointers.

 1   // Fig. 13.5: fig13_05.cpp
 2   // Aiming base-class and derived-class pointers at base-class
 3   // and derived-class objects, respectively.
 4   #include <iostream>
 5   using std::cout;
 6   using std::endl;
 7   using std::fixed;
 8
 9   #include <iomanip>
10   using std::setprecision;
11

12   // include class definitions
13   #include "CommissionEmployee.h"
14   #include "BasePlusCommissionEmployee.h"
15
16   int main()
17   {
18      // create base-class object
19      CommissionEmployee commissionEmployee(
20         "Sue""Jones""222-22-2222"10000.06 );
21
22      // create base-class pointer
23      CommissionEmployee *commissionEmployeePtr = 0;
24
25      // create derived-class object
26      BasePlusCommissionEmployee basePlusCommissionEmployee(
27         "Bob""Lewis""333-33-3333"5000.04300 );
28
29      // create derived-class pointer
30      BasePlusCommissionEmployee *basePlusCommissionEmployeePtr = 0;
31
32      // set floating-point output formatting
33      cout << fixed << setprecision( 2 );
34
35      // output objects commissionEmployee and basePlusCommissionEmployee
36      cout << "Print base-class and derived-class objects: ";
37      commissionEmployee.print(); // invokes base-class print
38      cout << " ";
39      basePlusCommissionEmployee.print(); // invokes derived-class print
40
41      // aim base-class pointer at base-class object and print         
42      commissionEmployeePtr = &commissionEmployee; // perfectly natural
43      cout << " Calling print with base-class pointer to "
44         << " base-class object invokes base-class print function: ";
45      commissionEmployeePtr->print(); // invokes base-class print
46
47      // aim derived-class pointer at derived-class object and print         
48      basePlusCommissionEmployeePtr = &basePlusCommissionEmployee; // natural
49      cout << " Calling print with derived-class pointer to "
50         << " derived-class object invokes derived-class "
51         << "print function: ";
52      basePlusCommissionEmployeePtr->print(); // invokes derived-class print
53
54      // aim base-class pointer at derived-class object and print
55      commissionEmployeePtr = &basePlusCommissionEmployee;       
56      cout << " Calling print with base-class pointer to "
57         << "derived-class object invokes base-class print "
58         << "function on that derived-class object: ";
59      commissionEmployeePtr->print(); // invokes base-class print
60      cout << endl;
61      return 0;
62   } // end main

Print base-class and derived-class objects:

commission employee: Sue Jones
social security number: 222-22-2222
gross sales: 10000.00
commission rate: 0.06

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04
base salary: 300.00


Calling print with base-class pointer to
base-class object invokes base-class print function:

commission employee: Sue Jones
social security number: 222-22-2222
gross sales: 10000.00
commission rate: 0.06


Calling print with derived-class pointer to
derived-class object invokes derived-class print function:

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04
base salary: 300.00


Calling print with base-class pointer to derived-class object
invokes base-class print function on that derived-class object:

commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04

13.3.2 Aiming Derived-Class Pointers at Base-Class Objects

In Section 13.3.1, we assigned the address of a derived-class object to a base-class pointer and explained that the C++ compiler allows this assignment, because a derived-class object is a base-class object. We take the opposite approach in Fig. 13.6, as we aim a derived-class pointer at a base-class object. [Note: This program uses classes CommissionEmployee and BasePlusCommissionEmployee of Figs. 13.113.4.] Lines 8–9 of Fig. 13.6 create a CommissionEmployee object, and line 10 creates a BasePlusCommissionEmployee pointer. Line 14 attempts to assign the address of base-class object commissionEmployee to derived-class pointer basePlusCommissionEmployeePtr, but the C++ compiler generates an error. The compiler prevents this assignment, because a CommissionEmployee is not a BasePlusCommissionEmployee. Consider the consequences if the compiler were to allow this assignment. Through a BasePlusCommissionEmployee pointer, we can invoke every BasePlusCommissionEmployee member function, including setBaseSalary, for the object to which the pointer points (i.e., the base-class object commissionEmployee). However, the CommissionEmployee object does not provide a setBaseSalary member function, nor does it provide a baseSalary data member to set. This could lead to problems, because member function setBaseSalary would assume that there is a baseSalary data member to set at its “usual location” in a BasePlusCommissionEmployee object. This memory does not belong to the CommissionEmployee object, so member function setBaseSalary might overwrite other important data in memory, possibly data that belongs to a different object.

Fig. 13.6 Aiming a derived-class pointer at a base-class object.

 1   // Fig. 13.6: fig13_06.cpp
 2   // Aiming a derived-class pointer at a base-class object.
 3   #include "CommissionEmployee.h"
 4   #include "BasePlusCommissionEmployee.h"
 5
 6   int main()
 7   {
 8      CommissionEmployee commissionEmployee(
 9         "Sue""Jones""222-22-2222"10000.06 );
10      BasePlusCommissionEmployee *basePlusCommissionEmployeePtr = 0;
11
12      // aim derived-class pointer at base-class object                 
13      // Error: a CommissionEmployee is not a BasePlusCommissionEmployee
14      basePlusCommissionEmployeePtr = &commissionEmployee;              
15      return 0;
16   } // end main
Borland C++ command-line compiler error messages:

Error E2034 Fig13_06fig13_06.cpp 14: Cannot convert 'CommissionEmployee *'
   to 'BasePlusCommissionEmployee *' in function main()

GNU C++ compiler error messages:

fig13_06.cpp:14: error: invalid conversion from 'CommissionEmployee*' to
   'BasePlusCommissionEmployee*'

Microsoft Visual C++ 2005 compiler error messages:

C:cppfp_examplesch13Fig13_06fig13_06.cpp(14) : error C2440:
   '=' : cannot convert from 'CommissionEmployee *__w64 ' to
   'BasePlusCommissionEmployee *'
         Cast from base to derived requires dynamic_cast or static_cast

13.3.3 Derived-Class Member-Function Calls via Base-Class Pointers

Off a base-class pointer, the compiler allows us to invoke only base-class member functions. Thus, if a base-class pointer is aimed at a derived-class object, and an attempt is made to access a derived-class-only member function, a compilation error will occur.

Figure 13.7 shows the consequences of attempting to invoke a derived-class member function off a base-class pointer. [Note: We are again using classes CommissionEmployee and BasePlusCommissionEmployee of Figs. 13.113.4.] Line 9 creates commissionEmployeePtr—a pointer to a CommissionEmployee object—and lines 10–11 create a BasePlusCommissionEmployee object. Line 14 aims commissionEmployeePtr at derived-class object basePlusCommissionEmployee. Recall from Section 13.3.1 that this is allowed, because a BasePlusCommissionEmployee is a CommissionEmployee (in the sense that a BasePlusCommissionEmployee object contains all the functionality of a CommissionEmployee object). Lines 18–22 invoke base-class member functions getFirstName, getLastName, getSocialSecurityNumber, getGrossSales and getCommissionRate off the base-class pointer. All of these calls are legitimate, because BasePlusCommissionEmployee inherits these member functions from CommissionEmployee. We know that commissionEmployeePtr is aimed at a BasePlusCommissionEmployee object, so in lines 26–27 we attempt to invoke BasePlusCommissionEmployee member functions getBaseSalary and setBaseSalary. The compiler generates errors on both of these calls, because they are not made to member functions of base-class CommissionEmployee. The handle can be used to invoke only those functions that are members of that handle’s associated class type. (In this case, off a CommissionEmployee *, we can invoke only CommissionEmployee member functions setFirstName, getFirstName, setLastName, getLastName, setSocialSecurityNumber, getSocialSecurityNumber, setGrossSales, getGrossSales, setCommissionRate, getCommissionRate, earnings and print.)

Fig. 13.7 Attempting to invoke derived-class-only functions via a base-class pointer.

 1   // Fig. 13.7: fig13_07.cpp
 2   // Attempting to invoke derived-class-only member functions
 3   // through a base-class pointer.
 4   #include "CommissionEmployee.h"
 5   #include "BasePlusCommissionEmployee.h"
 6
 7   int main()
 8   {
 9      CommissionEmployee *commissionEmployeePtr = 0// base class
10      BasePlusCommissionEmployee basePlusCommissionEmployee(
11         "Bob""Lewis""333-33-3333"5000.04300 ); // derived class
12
13      // aim base-class pointer at derived-class object
14      commissionEmployeePtr = &basePlusCommissionEmployee;
15
16      // invoke base-class member functions on derived-class
17      // object through base-class pointer (allowed)
18      string firstName = commissionEmployeePtr->getFirstName();
19      string lastName = commissionEmployeePtr->getLastName();
20      string ssn = commissionEmployeePtr->getSocialSecurityNumber();
21      double grossSales = commissionEmployeePtr->getGrossSales();
22      double commissionRate = commissionEmployeePtr->getCommissionRate();
23
24      // attempt to invoke derived-class-only member functions          
25      // on derived-class object through base-class pointer (disallowed)
26      double baseSalary = commissionEmployeePtr->getBaseSalary();       
27      commissionEmployeePtr->setBaseSalary( 500 );                      
28      return 0;
29   } // end main

Borland C++ command-line compiler error messages:

Error E2316 Fig13_07fig13_07.cpp 26: 'getBaseSalary' is not a member of
   'CommissionEmployee' in function main()
Error E2316 Fig13_07fig13_07.cpp 27: 'setBaseSalary' is not a member of
   'CommissionEmployee' in function main()

Microsoft Visual C++ 2005 compiler error messages:

C:cppfp_examplesch13Fig13_07fig13_07.cpp(26) : error C2039:
   'getBaseSalary' : is not a member of 'CommissionEmployee'
      C:cppfp_examplesch13Fig13_07CommissionEmployee.h(10) :
         see declaration of 'CommissionEmployee'
C:cppfp_examplesch13Fig13_07fig13_07.cpp(27) : error C2039:
   'setBaseSalary' : is not a member of 'CommissionEmployee'
      C:cppfp_examplesch13Fig13_07CommissionEmployee.h(10) :
         see declaration of 'CommissionEmployee'

GNU C++ compiler error messages:

fig13_07.cpp:26: error: `getBaseSalary' undeclared (first use this function)
fig13_07.cpp:26: error: (Each undeclared identifier is reported only once for
   each function it appears in.)
fig13_07.cpp:27: error: `setBaseSalary' undeclared (first use this function)

The compiler does allow access to derived-class-only members from a base-class pointer that is aimed at a derived-class object if we explicitly cast the base-class pointer to a derived-class pointer—a technique known as downcasting. As you learned in Section 13.3.1, it is possible to aim a base-class pointer at a derived-class object. However, as we demonstrated in Fig. 13.7, a base-class pointer can be used to invoke only the functions declared in the base class. Downcasting allows a derived-class-specific operation on a derived-class object pointed to by a base-class pointer. After a downcast, the program can invoke derived-class functions that are not in the base class. We’ll show you a concrete example of downcasting in Section 13.8.

Software Engineering Observation 13.3

Software Engineering Observation 13.3

If the address of a derived-class object has been assigned to a pointer of one of its direct or indirect base classes, it is acceptable to cast that base-class pointer back to a pointer of the derived-class type. In fact, this must be done to send that derived-class object messages that do not appear in the base class.

13.3.4 Virtual Functions

In Section 13.3.1, we aimed a base-class CommissionEmployee pointer at a derived-class BasePlusCommissionEmployee object, then invoked member function print through that pointer. Recall that the type of the handle determines which class’s functionality to invoke. In that case, the CommissionEmployee pointer invoked the CommissionEmployee member function print on the BasePlusCommissionEmployee object, even though the pointer was aimed at a BasePlusCommissionEmployee object that has its own customized print function. With virtual functions, the type of the object being pointed to, not the type of the handle, determines which version of a virtual function to invoke.

First, we consider why virtual functions are useful. Suppose that a set of shape classes such as Circle, Triangle, Rectangle and Square are all derived from base class Shape. Each of these classes might be endowed with the ability to draw itself via a member function draw. Although each class has its own draw function, the function for each shape is quite different. In a program that draws a set of shapes, it would be useful to be able to treat all the shapes generically as objects of the base class Shape. Then, to draw any shape, we could simply use a base-class Shape pointer to invoke function draw and let the program determine dynamically (i.e., at runtime) which derived-class draw function to use, based on the type of the object to which the base-class Shape pointer points at any given time.

To enable this kind of behavior, we declare draw in the base class as a virtual function, and we override draw in each of the derived classes to draw the appropriate shape. From an implementation perspective, overriding a function is no different than redefining one (which is the approach we have been using until now). An overridden function in a derived class has the same signature and return type (i.e., prototype) as the function it overrides in its base class. If we do not declare the base-class function as virtual, we can redefine that function. By contrast, if we declare the base-class function as virtual, we can override that function to enable polymorphic behavior. We declare a virtual function by preceding the function’s prototype with the keyword virtual in the base class. For example,

virtual void draw() const;

would appear in base class Shape. The preceding prototype declares that function draw is a virtual function that takes no arguments and returns nothing. This function is declared const because a draw function typically would not make changes to the Shape object on which it is invoked—virtual functions do not have to be const functions.

Software Engineering Observation 13.4

Software Engineering Observation 13.4

Once a function is declared virtual, it remains virtual all the way down the inheritance hierarchy from that point, even if that function is not explicitly declared virtual when a derived class overrides it.

Good Programming Practice 13.1

Good Programming Practice 13.1

Even though certain functions are implicitly virtual because of a declaration made higher in the class hierarchy, explicitly declare these functions virtual at every level of the hierarchy to promote program clarity.

Error-Prevention Tip 13.1

Error-Prevention Tip 13.1

When a programmer browses a class hierarchy to locate a class to reuse, it is possible that a function in that class will exhibit virtual function behavior even though it is not explicitly declared virtual. This happens when the class inherits a virtual function from its base class, and it can lead to subtle logic errors. Such errors can be avoided by explicitly declaring all virtual functions virtual throughout the inheritance hierarchy.

Software Engineering Observation 13.5

Software Engineering Observation 13.5

When a derived class chooses not to override a virtual function from its base class, the derived class simply inherits its base class’s virtual function implementation.

If a program invokes a virtual function through a base-class pointer to a derived-class object (e.g., shapePtr->draw()), the program will choose the correct derived-class draw function dynamically (i.e., at execution time) based on the object type—not the pointer type. Choosing the appropriate function to call at execution time (rather than at compile time) is known as dynamic binding or late binding.

When a virtual function is called by referencing a specific object by name and using the dot member-selection operator (e.g., squareObject.draw()), the function invocation is resolved at compile time (this is called static binding) and the virtual function that is called is the one defined for (or inherited by) the class of that particular object—this is not polymorphic behavior. Thus, dynamic binding with virtual functions occurs only off pointer (and, as we’ll soon see, reference) handles.

Now let’s see how virtual functions can enable polymorphic behavior in our employee hierarchy. Figures 13.813.9 are the header files for classes CommissionEmployee and BasePlusCommissionEmployee, respectively. Note that the only difference between these files and those of Fig. 13.1 and Fig. 13.3 is that we specify each class’s earnings and print member functions as virtual (lines 30–31 of Fig. 13.8 and lines 21–22 of Fig. 13.9). Because functions earnings and print are virtual in class CommissionEmployee, class BasePlusCommissionEmployee’s earnings and print functions override class CommissionEmployee’s. Now, if we aim a base-class CommissionEmployee pointer at a derived-class BasePlusCommissionEmployee object, and the program uses that pointer to call either function earnings or print, the BasePlusCommissionEmployee object’s corresponding function will be invoked. There were no changes to the member-function implementations of classes CommissionEmployee and BasePlusCommissionEmployee, so we reuse the versions of Fig. 13.2 and Fig. 13.4.

Fig. 13.8 CommissionEmployee class header file declares earnings and print functions as virtual.

 1   // Fig. 13.8: CommissionEmployee.h
 2   // CommissionEmployee class definition represents a commission employee.
 3   #ifndef COMMISSION_H
 4   #define COMMISSION_H
 5
 6   #include <string> // C++ standard string class
 7   using std::string;
 8
 9   class CommissionEmployee
10   {
11   public:
12      CommissionEmployee( const string &, const string &, const string &,
13         double = 0.0double = 0.0 );
14
15      void setFirstName( const string & ); // set first name
16      string getFirstName() const// return first name
17
18      void setLastName( const string & ); // set last name
19      string getLastName() const// return last name
20
21      void setSocialSecurityNumber( const string & ); // set SSN
22      string getSocialSecurityNumber() const// return SSN
23
24      void setGrossSales( double ); // set gross sales amount
25      double getGrossSales() const// return gross sales amount
26
27      void setCommissionRate( double ); // set commission rate
28      double getCommissionRate() const// return commission rate
29
30      virtual double earnings() const// calculate earnings        
31      virtual void print() const// print CommissionEmployee object
32   private:
33      string firstName;
34      string lastName;
35      string socialSecurityNumber;
36      double grossSales; // gross weekly sales
37      double commissionRate; // commission percentage
38   }; // end class CommissionEmployee
39
40   #endif

Fig. 13.9 BasePlusCommissionEmployee class header file declares earnings and print functions as virtual.

 1   // Fig. 13.9: BasePlusCommissionEmployee.h
 2   // BasePlusCommissionEmployee class derived from class
 3   // CommissionEmployee.
 4   #ifndef BASEPLUS_H
 5   #define BASEPLUS_H
 6

7   #include <string> // C++ standard string class
 8   using std::string;
 9
10   #include "CommissionEmployee.h" // CommissionEmployee class declaration
11
12   class BasePlusCommissionEmployee : public CommissionEmployee
13   {
14   public:
15      BasePlusCommissionEmployee( const string &, const string &,
16         const string &, double = 0.0double = 0.0double = 0.0 );
17
18      void setBaseSalary( double ); // set base salary
19      double getBaseSalary() const// return base salary
20
21      virtual double earnings() const// calculate earnings                
22      virtual void print() const// print BasePlusCommissionEmployee object
23   private:
24      double baseSalary; // base salary
25   }; // end class BasePlusCommissionEmployee
26
27   #endif

We modified Fig. 13.5 to create the program of Fig. 13.10. Lines 46–57 demonstrate again that a CommissionEmployee pointer aimed at a CommissionEmployee object can be used to invoke CommissionEmployee functionality, and a BasePlusCommissionEmployee pointer aimed at a BasePlusCommissionEmployee object can be used to invoke BasePlusCommissionEmployee functionality. Line 60 aims base-class pointer commissionEmployeePtr at derived-class object basePlusCommissionEmployee. Note that when line 67 invokes member function print off the base-class pointer, the derived-class BasePlusCommissionEmployee’s print member function is invoked, so line 67 outputs different text than line 59 does in Fig. 13.5 (when member function print was not declared virtual). We see that declaring a member function virtual causes the program to dynamically determine which function to invoke based on the type of object to which the handle points, rather than on the type of the handle. Note again that when commissionEmployeePtr points to a CommissionEmployee object (line 46), class CommissionEmployee’s print function is invoked, and when CommissionEmployeePtr points to a BasePlusCommissionEmployee object, class BasePlusCommissionEmployee’s print function is invoked. Thus, the same message—print, in this case—sent (off a base-class pointer) to a variety of objects related by inheritance to that base class, takes on many forms—this is polymorphic behavior.

Fig. 13.10 Demonstrating polymorphism by invoking a derived-class virtual function via a base-class pointer to a derived-class object.

 1   // Fig. 13.10: fig13_10.cpp
 2   // Introducing polymorphism, virtual functions and dynamic binding.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6   using std::fixed;
 7
 8   #include <iomanip>
 9   using std::setprecision;
10
11   // include class definitions
12   #include "CommissionEmployee.h"
13   #include "BasePlusCommissionEmployee.h"

14
15   int main()
16   {
17      // create base-class object
18      CommissionEmployee commissionEmployee(
19         "Sue""Jones""222-22-2222"10000.06 );
20
21      // create base-class pointer
22      CommissionEmployee *commissionEmployeePtr = 0;
23
24      // create derived-class object
25      BasePlusCommissionEmployee basePlusCommissionEmployee(
26         "Bob""Lewis""333-33-3333"5000.04300 );
27
28      // create derived-class pointer
29      BasePlusCommissionEmployee *basePlusCommissionEmployeePtr = 0;
30
31      // set floating-point output formatting
32      cout << fixed << setprecision( 2 );
33
34      // output objects using static binding
35      cout << "Invoking print function on base-class and derived-class "
36         << " objects with static binding ";
37      commissionEmployee.print(); // static binding
38      cout << " ";
39      basePlusCommissionEmployee.print(); // static binding
40
41      // output objects using dynamic binding
42      cout << " Invoking print function on base-class and "
43         << "derived-class  objects with dynamic binding";
44
45      // aim base-class pointer at base-class object and print
46      commissionEmployeePtr = &commissionEmployee;            
47      cout << " Calling virtual function print with base-class pointer"
48         << " to base-class object invokes base-class "
49         << "print function: ";
50      commissionEmployeePtr->print(); // invokes base-class print
51
52      // aim derived-class pointer at derived-class object and print
53      basePlusCommissionEmployeePtr = &basePlusCommissionEmployee;  
54      cout << " Calling virtual function print with derived-class "
55         << "pointer to derived-class object invokes derived-class "
56         << "print function: ";
57      basePlusCommissionEmployeePtr->print(); // invokes derived-class print
58
59      // aim base-class pointer at derived-class object and print
60      commissionEmployeePtr = &basePlusCommissionEmployee;       
61      cout << " Calling virtual function print with base-class pointer"
62         << " to derived-class object invokes derived-class "
63         << "print function: ";
64

65      // polymorphism; invokes BasePlusCommissionEmployee's print;
66      // base-class pointer to derived-class object               
67      commissionEmployeePtr->print();                             
68      cout << endl;
69      return 0;
70   } // end main

Invoking print function on base-class and derived-class
objects with static binding

commission employee: Sue Jones
social security number: 222-22-2222
gross sales: 10000.00
commission rate: 0.06

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04
base salary: 300.00


Invoking print function on base-class and derived-class
objects with dynamic binding

Calling virtual function print with base-class pointer
to base-class object invokes base-class print function:

commission employee: Sue Jones
social security number: 222-22-2222
gross sales: 10000.00
commission rate: 0.06

Calling virtual function print with derived-class pointer
to derived-class object invokes derived-class print function:

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04
base salary: 300.00

Calling virtual function print with base-class pointer
to derived-class object invokes derived-class print function:

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04
base salary: 300.00

13.3.5 Summary of the Allowed Assignments Between Base-Class and Derived-Class Objects and Pointers

Now that you have seen a complete application that processes diverse objects polymorphically, we summarize what you can and cannot do with base-class and derived-class objects and pointers. Although a derived-class object also is a base-class object, the two objects are nevertheless different. As discussed previously, derived-class objects can be treated as if they were base-class objects. This is a logical relationship, because the derived class contains all the members of the base class. However, base-class objects cannot be treated as if they were derived-class objects—the derived class can have additional derived-class-only members. For this reason, aiming a derived-class pointer at a base-class object is not allowed without an explicit cast—such an assignment would leave the derived-class-only members undefined on the base-class object. The cast relieves the compiler of the responsibility of issuing an error message. In a sense, by using the cast you are saying, “I know that what I’m doing is dangerous and I take full responsibility for my actions.”

In the current section and in Chapter 12, we have discussed four ways to aim base-class pointers and derived-class pointers at base-class objects and derived-class objects:

1.   Aiming a base-class pointer at a base-class object is straightforward—calls made off the base-class pointer simply invoke base-class functionality.

2.   Aiming a derived-class pointer at a derived-class object is straightforward—calls made off the derived-class pointer simply invoke derived-class functionality.

3.   Aiming a base-class pointer at a derived-class object is safe, because the derived-class object is an object of its base class. However, this pointer can be used to invoke only base-class member functions. If you attempt to refer to a derived-class-only member through the base-class pointer, the compiler reports an error. To avoid this error, you must cast the base-class pointer to a derived-class pointer. The derived-class pointer can then be used to invoke the derived-class object’s complete functionality. This technique, called downcasting, is a potentially dangerous operation—Section 13.8 demonstrates how to safely use downcasting. If a virtual function is defined in the base and derived classes (either by inheritance or overriding), and if that function is invoked on a derived-class object via a base-class pointer, then the derived-class version of that function is called. This is an example of the polymorphic behavior that occurs only with virtual functions.

4.   Aiming a derived-class pointer at a base-class object generates a compilation error. The is-a relationship applies only from a derived class to its direct and indirect base classes, and not vice versa. A base-class object does not contain the derived-class-only members that can be invoked off a derived-class pointer.

Common Programming Error 13.1

Common Programming Error 13.1

After aiming a base-class pointer at a derived-class object, attempting to reference derived-class-only members with the base-class pointer is a compilation error.

Common Programming Error 13.2

Common Programming Error 13.2

Treating a base-class object as a derived-class object can cause errors.

13.4 Type Fields and switch Statements

One way to determine the type of an object that is incorporated in a larger program is to use a switch statement. This allows us to distinguish among object types, then invoke an appropriate action for a particular object. For example, in a hierarchy of shapes in which each shape object has a shapeType attribute, a switch statement could check the object’s shapeType to determine which print function to call.

Using switch logic exposes programs to a variety of potential problems. For example, you might forget to include a type test when one is warranted, or might forget to test all possible cases in a switch statement. When modifying a switch-based system by adding new types, you might forget to insert the new cases in all relevant switch statements. Every addition or deletion of a class requires the modification of every switch statement in the system; tracking these statements down can be time consuming and error prone.

Software Engineering Observation 13.6

Software Engineering Observation 13.6

Polymorphic programming can eliminate the need for switch logic. By using the polymorphism mechanism to perform the equivalent logic, programmers can avoid the kinds of errors typically associated with switch logic.

Software Engineering Observation 13.7

Software Engineering Observation 13.7

An interesting consequence of using polymorphism is that programs take on a simplified appearance. They contain less branching logic and simpler sequential code. This simplification facilitates testing, debugging and program maintenance.

13.5 Abstract Classes and Pure virtual Functions

When we think of a class as a type, we assume that programs will create objects of that type. However, there are cases in which it is useful to define classes from which you never intend to instantiate any objects. Such classes are called abstract classes. Because these classes normally are used as base classes in inheritance hierarchies, we refer to them as abstract base classes. These classes cannot be used to instantiate objects, because, as we’ll soon see, abstract classes are incomplete—derived classes must define the “missing pieces.” We build programs with abstract classes in Section 13.6.

The purpose of an abstract class is to provide an appropriate base class from which other classes can inherit. Classes that can be used to instantiate objects are called concrete classes. Such classes provide implementations of every member function they define. We could have an abstract base class TwoDimensionalShape and derive such concrete classes as Square, Circle and Triangle. We could also have an abstract base class ThreeDimensionalShape and derive such concrete classes as Cube, Sphere and Cylinder. Abstract base classes are too generic to define real objects; we need to be more specific before we can think of instantiating objects. For example, if someone tells you to “draw the two-dimensional shape,” what shape would you draw? Concrete classes provide the specifics that make it reasonable to instantiate objects.

An inheritance hierarchy does not need to contain any abstract classes, but, as we’ll see, many object-oriented systems have class hierarchies headed by abstract base classes. In some cases, abstract classes constitute the top few levels of the hierarchy. A good example of this is the shape hierarchy in Fig. 12.3, which begins with abstract base class Shape. On the next level of the hierarchy we have two more abstract base classes, namely, TwoDimensionalShape and ThreeDimensionalShape. The next level of the hierarchy defines concrete classes for two-dimensional shapes (namely, Circle, Square and Triangle) and for three-dimensional shapes (namely, Sphere, Cube and Tetrahedron).

A class is made abstract by declaring one or more of its virtual functions to be “pure.” A pure virtual function is specified by placing “= 0” in its declaration, as in

virtual void draw() const = 0// pure virtual function

The “= 0” is known as a pure specifier. Pure virtual functions do not provide implementations. Every concrete derived class must override all base-class pure virtual functions with concrete implementations of those functions. The difference between a virtual function and a pure virtual function is that a virtual function has an implementation and gives the derived class the option of overriding the function; by contrast, a pure virtual function does not provide an implementation and requires the derived class to override the function for that derived class to be concrete; otherwise the derived class remains abstract.

Pure virtual functions are used when it does not make sense for the base class to have an implementation of a function, but you want all concrete derived classes to implement the function. Returning to our earlier example of space objects, it does not make sense for the base class SpaceObject to have an implementation for function draw (as there is no way to draw a generic space object without having more information about what type of space object is being drawn). An example of a function that would be defined as virtual (and not pure virtual) would be one that returns a name for the object. We can name a generic SpaceObject (for instance, as "space object"), so a default implementation for this function can be provided, and the function does not need to be pure virtual. The function is still declared virtual, however, because it is expected that derived classes will override this function to provide more specific names for the derived-class objects.

Software Engineering Observation 13.8

Software Engineering Observation 13.8

An abstract class defines a common public interface for the various classes in a class hierarchy. An abstract class contains one or more pure virtual functions that concrete derived classes must override.

Common Programming Error 13.3

Common Programming Error 13.3

Attempting to instantiate an object of an abstract class causes a compilation error.

Common Programming Error 13.4

Common Programming Error 13.4

Failure to override a pure virtual function in a derived class, then attempting to instantiate objects of that class, is a compilation error.

Software Engineering Observation 13.9

Software Engineering Observation 13.9

An abstract class has at least one pure virtual function. An abstract class also can have data members and concrete functions (including constructors and destructors), which are subject to the normal rules of inheritance by derived classes.

Although we cannot instantiate objects of an abstract base class, we can use the abstract base class to declare pointers and references that can refer to objects of any concrete classes derived from the abstract class. Programs typically use such pointers and references to manipulate derived-class objects polymorphically.

Consider another application of polymorphism. A screen manager needs to display a variety of objects, including new types of objects that you’ll add to the system after writing the screen manager. The system might need to display various shapes, such as Circles, Triangles or Rectangles, which are derived from abstract base class Shape. The screen manager uses Shape pointers to manage the objects that are displayed. To draw any object (regardless of the level at which that object’s class appears in the inheritance hierarchy), the screen manager uses a base-class pointer to the object to invoke the object’s draw function, which is a pure virtual function in base class Shape; therefore, each concrete derived class must implement function draw. Each Shape object in the inheritance hierarchy knows how to draw itself. The screen manager does not have to worry about the type of each object or whether the screen manager has ever encountered objects of that type.

Polymorphism is particularly effective for implementing layered software systems. In operating systems, for example, each type of physical device could operate quite differently from the others. Even so, commands to read or write data from and to devices may have a certain uniformity. The write message sent to a device-driver object needs to be interpreted specifically in the context of that device driver and how that device driver manipulates devices of a specific type. However, the write call itself really is no different from the write to any other device in the system—place some number of bytes from memory onto that device. An object-oriented operating system might use an abstract base class to provide an interface appropriate for all device drivers. Then, through inheritance from that abstract base class, derived classes are formed that all operate similarly. The capabilities (i.e., the public functions) offered by the device drivers are provided as pure virtual functions in the abstract base class. The implementations of these pure virtual functions are provided in the derived classes that correspond to the specific types of device drivers. This architecture also allows new devices to be added to a system easily, even after the operating system has been defined. The user can just plug in the device and install its new device driver. The operating system “talks” to this new device through its device driver, which has the same public member functions as all other device drivers—those defined in the device driver abstract base class.

It is common in object-oriented programming to define an iterator class that can traverse all the objects in a container (such as an array). For example, a program can print a list of objects in a vector by creating an iterator object, then using the iterator to obtain the next element of the list each time the iterator is called. Iterators often are used in polymorphic programming to traverse an array or a linked list of pointers to objects from various levels of a hierarchy. The pointers in such a list are all base-class pointers. (Chapter 20, Standard Template Library (STL), presents a thorough treatment of iterators.) A list of pointers to objects of base class TwoDimensionalShape could contain pointers to objects of classes Square, Circle, Triangle and so on. Using polymorphism to send a draw message, off a TwoDimensionalShape * pointer, to each object in the list would draw each object correctly on the screen.

13.6 Case Study: Payroll System Using Polymorphism

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 BasePlusCommissionEmployee—derived from CommissionEmployee—represents the last employee type. The UML class diagram in Fig. 13.11 shows the inheritance hierarchy for our polymorphic employee payroll application. Note that the abstract class name Employee is italicized, as per the convention of the UML.

Fig. 13.11 Employee hierarchy UML class diagram.

Employee hierarchy UML class diagram.

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.

Software Engineering Observation 13.10

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 hierarchy—each 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 hierarchy—a 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 function—there 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 class—because 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.

Fig. 13.12 Polymorphic interface for the Employee hierarchy classes.

Polymorphic interface for the Employee hierarchy classes.

Fig. 13.13 Employee class header file.

 1   // Fig. 13.13: Employee.h
 2   // Employee abstract base class.
 3   #ifndef EMPLOYEE_H
 4   #define EMPLOYEE_H
 5
 6   #include <string> // 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

Fig. 13.14 Employee class implementation file.

 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 <iostream>
 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

Function print in class Employee displays the first name, last name and social security number of the employee. As we’ll 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 classes—each of these functions is inherited and used “as is” by each of the derived classes.

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).

Recall that we declared earnings as a pure virtual function because first we 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 10–15) does not validate the social security number. Normally, such validation should be provided.

Note that virtual function print (Fig. 13.14, lines 54–58) 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 11–12); 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 (line 19) 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.

Fig. 13.15 SalariedEmployee class header file.

 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

Fig. 13.16 SalariedEmployee class implementation file.

 1   // Fig. 13.16: SalariedEmployee.cpp
 2   // SalariedEmployee class member-function definitions.
 3   #include <iostream>
 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

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 30–33) overrides pure virtual function earnings in Employee to provide a concrete implementation that returns the SalariedEmployee’s weekly salary. If we did 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 18–19 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.

Function print of class SalariedEmployee (lines 36–41 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 function 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 11–12) 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).

Fig. 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.0double = 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

Fig. 13.18 HourlyEmployee class implementation file.

 1   // Fig. 13.18: HourlyEmployee.cpp
 2   // HourlyEmployee class member-function definitions.
 3   #include <iostream>
 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

Figure 13.18 contains the member-function implementations for class HourlyEmployee. Lines 18–21 and 30–34 define set functions that assign new values to data members wage and hours, respectively. Function setWage (lines 18–21) ensures that wage is nonnegative, and function setHours (lines 30–34) 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 24–27 and 37–40. 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.

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 9–15) that takes a first name, a last name, a social security number, a sales amount and a commission rate; set functions (lines 18–21 and 30–33) to assign new values to data members commissionRate and grossSales, respectively; get functions (lines 24–27 and 36–39) that retrieve the values of these data members; function earnings (lines 43–46) to calculate a CommissionEmployee’s earnings; and function print (lines 49–55), 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).

Fig. 13.19 CommissionEmployee class header file.

 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.0double = 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

Fig. 13.20 CommissionEmployee class implementation file.

 1   // Fig. 13.20: CommissionEmployee.cpp
 2   // CommissionEmployee class member-function definitions.
 3   #include <iostream>

 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 10–16 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 19–22) to assign a new value to data member baseSalary and a get function (lines 25–28) to return baseSalary’s value. Function earnings (lines 32–35) 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 38–43) 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.

Fig. 13.21 BasePlusCommissionEmployee class header file.

 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.0double = 0.0double = 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

Fig. 13.22 BasePlusCommissionEmployee class implementation file.

 1   // Fig. 13.22: BasePlusCommissionEmployee.cpp
 2   // BasePlusCommissionEmployee member-function definitions.
 3   #include <iostream>
 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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset