In this chapter you’ll learn:
What polymorphism is, how it makes programming more convenient and how it makes systems more extensible and maintainable.
To declare and use virtual
functions to effect polymorphism.
The distinction between abstract and concrete classes.
To declare pure virtual
functions to create abstract classes.
How to use runtime type information (RTTI) with downcasting, dynamic_cast, typeid
and type_info
.
How C++ implements virtual
functions and dynamic binding “under the hood.”
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
In Chapters 9–12, 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, SalariedEmployee
s are paid a fixed weekly salary regardless of the number of hours worked, while HourlyEmployee
s 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.
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 Quadrilateral
s, such as Square
s, Parallelogram
s and Trapezoid
s. 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.
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).
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.
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.
The example in Figs. 13.1–13.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.1–13.2), which we discussed in Chapter 12, is used to represent employees who are paid a percentage of their sales. Class BasePlusCommissionEmployee
(Figs. 13.3–13.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.0, double = 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.0, double = 0.0, double = 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, .04, 300 );
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
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.1–13.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*'
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
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.1–13.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, .04, 300 ); // 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.
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.
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.
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.
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.
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.
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.8–13.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.0, double = 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.0, double = 0.0, double = 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, .04, 300 );
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
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.
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.
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.
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.
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.
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.
Attempting to instantiate an object of an abstract class causes a compilation error.
Failure to override a pure virtual
function in a derived class, then attempting to instantiate objects of that class, is a compilation error.
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 Circle
s, Triangle
s or Rectangle
s, 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.
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.
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
.
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.
Class Employee
(Figs. 13.13–13.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.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.
Class SalariedEmployee
(Figs. 13.15–13.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.
Class HourlyEmployee
(Figs. 13.17–13.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.0, double = 0.0 );
13
14 void setWage( double ); // set hourly wage
15 double getWage() const; // return hourly wage
16
17 void setHours( double ); // set hours worked
18 double getHours() const; // return hours worked
19
20 // keyword virtual signals intent to override
21 virtual double earnings() const; // calculate earnings
22 virtual void print() const; // print HourlyEmployee object
23 private:
24 double wage; // wage per hour
25 double hours; // hours worked for week
26 }; // end class HourlyEmployee
27
28 #endif // HOURLY_H
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.
Class CommissionEmployee
(Figs. 13.19–13.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.0, double = 0.0 );
13
14 void setCommissionRate( double ); // set commission rate
15 double getCommissionRate() const; // return commission rate
16
17 void setGrossSales( double ); // set gross sales amount
18 double getGrossSales() const; // return gross sales amount
19
20 // keyword virtual signals intent to override
21 virtual double earnings() const; // calculate earnings
22 virtual void print() const; // print CommissionEmployee object
23 private:
24 double grossSales; // gross weekly sales
25 double commissionRate; // commission percentage
26 }; // end class CommissionEmployee
27
28 #endif // COMMISSION_H
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
Class BasePlusCommissionEmployee
(Figs. 13.21–13.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.0, double = 0.0, double = 0.0 );
13
14 void setBaseSalary( double ); // set base salary
15 double getBaseSalary() const; // return base salary
16
17 // keyword virtual signals intent to override
18 virtual double earnings() const; // calculate earnings
19 virtual void print() const; // print BasePlusCommissionEmployee object
20 private:
21 double baseSalary; // base salary per week
22 }; // end class BasePlusCommissionEmployee
23
24 #endif // BASEPLUS_H
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