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. Toward the end of this section, 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 functionalityi.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 functionalitywhen 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 a derived-class object to the base-class pointer and use that pointer to invoke derived-class functionalityprecisely 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 12a 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 straightforwardwe 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 3235 of Fig. 13.4) redefines class CommissionEmployee's earnings member function (lines 7982 of Fig. 13.2) to include the object's base salary. Class BasePlusCommissionEmployee's print member function (lines 3846 of Fig. 13.4) redefines class CommissionEmployee's print member function (lines 8592 of Fig. 13.2) to display the same information as the print function in class CommissionEmployee, as well as the employee's base salary.


Figure 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  // 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

Figure 13.2. CommissionEmployee class implementation file.

(This item is displayed on pages 692 - 693 in the print version)

 1 // Fig. 13.2: CommissionEmployee.cpp
 2 // Class CommissionEmployee member-function definitions.
 3 #include 
 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 

Figure 13.3. BasePlusCommissionEmployee class header file.

(This item is displayed on pages 693 - 694 in the print version)

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

Figure 13.4. BasePlusCommissionEmployee class implementation file.

(This item is displayed on pages 694 - 695 in the print version)

 1 // Fig. 13.4: BasePlusCommissionEmployee.cpp
 2 // Class BasePlusCommissionEmployee member-function definitions.
 3 #include 
 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 1920 create a CommissionEmployee object and line 23 creates a pointer to a CommissionEmployee object; lines 2627 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. The C++ compiler allows this "crossover" 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 will see that this is crucial to implementing polymorphic behaviorthe key topic of this chapter.


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

(This item is displayed on pages 695 - 697 in the print version)

 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 
 5 using std::cout;
 6 using std::endl;
 7 using std::fixed;
 8
 9 #include 
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
 

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

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

(This item is displayed on pages 698 - 699 in the print version)

 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++.NET compiler error messages:

 C:cpphtp5_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 bases-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 commissionEmployeePtra pointer to a CommissionEmployee objectand lines 1011 create a BasePlusCommissionEmployee object. Line 14 aims commissionEmployeePtr at derived-class object basePlusCommissionEmployee. Recall from Section 13.3.1 that the C++ compiler allows this, because a BasePlusCommissionEmployee is a CommissionEmployee (in the sense that a BasePlusCommissionEmployee object contains all the functionality of a CommissionEmployee object). Lines 1822 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 2627 we attempt to invoke BasePlusCommissionEmployee member functions getBaseSalary and setBaseSalary. The C++ compiler generates errors on both of these lines, because these are not member functions of base-class CommissionEmployee. The handle can 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.)


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

(This item is displayed on pages 699 - 700 in the print version)

 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
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 
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++.NET compiler error messages:

 C:cpphtp5_examplesch13Fig13_07fig13_07.cpp(26) : error C2039:
 'getBaseSalary' : is not a member of 'CommissionEmployee'
 C:cpphtp5_examplesch13Fig13_07CommissionEmployee.h(10) :
 see declaration of 'CommissionEmployee'
 C:cpphtp5_examplesch13Fig13_07fig13_07.cpp(27) : error C2039:
 'setBaseSalary' : is not a member of 'CommissionEmployee'
 C:cpphtp5_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)
 

It turns out that the C++ 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 pointera 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 program to perform 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 will show you a concrete example of downcasting in Section 13.8.


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. The 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 necessarily have to be const functions.

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 class overrides it.


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

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

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 the 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 typenot 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 objectthis is not polymorphic behavior. Thus, dynamic binding with virtual functions occurs only off pointer (and, as we will 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 3031 of Fig. 13.8 and lines 2122 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.

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

(This item is displayed on page 703 in the print version)

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

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

(This item is displayed on pages 703 - 704 in the print version)

 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  // 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 4657 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. The decision about which function to call is an example of polymorphism. 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 messageprint, in this casesent (off a base-class pointer) to a variety of objects related by inheritance to that base class, takes on many formsthis is polymorphic behavior.


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

(This item is displayed on pages 704 - 706 in the print version)

 1 // Fig. 13.10: fig13_10.cpp
 2 // Introducing polymorphism, virtual functions and dynamic binding.
 3 #include 
 4 using std::cout;
 5 using std::endl;
 6 using std::fixed;
 7
 8 #include 
 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
 

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 are 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 are derived-class objectsthe 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 castsuch 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 straightforwardcalls made off the base-class pointer simply invoke base-class functionality.
  2. Aiming a derived-class pointer at a derived-class object is straightforwardcalls 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 the programmer attempts to refer to a derived-class-only member through the base-class pointer, the compiler reports an error. To avoid this error, the programmer 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. However, this techniquecalled downcastingis a potentially dangerous operation. Section 13.8 demonstrates how to safely use downcasting.
  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

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

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


Introduction to Computers, the Internet and World Wide Web

Introduction to C++ Programming

Introduction to Classes and Objects

Control Statements: Part 1

Control Statements: Part 2

Functions and an Introduction to Recursion

Arrays and Vectors

Pointers and Pointer-Based Strings

Classes: A Deeper Look, Part 1

Classes: A Deeper Look, Part 2

Operator Overloading; String and Array Objects

Object-Oriented Programming: Inheritance

Object-Oriented Programming: Polymorphism

Templates

Stream Input/Output

Exception Handling

File Processing

Class string and String Stream Processing

Web Programming

Searching and Sorting

Data Structures

Bits, Characters, C-Strings and structs

Standard Template Library (STL)

Other Topics

Appendix A. Operator Precedence and Associativity Chart

Appendix B. ASCII Character Set

Appendix C. Fundamental Types

Appendix D. Number Systems

Appendix E. C Legacy Code Topics

Appendix F. Preprocessor

Appendix G. ATM Case Study Code

Appendix H. UML 2: Additional Diagram Types

Appendix I. C++ Internet and Web Resources

Appendix J. Introduction to XHTML

Appendix K. XHTML Special Characters

Appendix L. Using the Visual Studio .NET Debugger

Appendix M. Using the GNU C++ Debugger

Bibliography



C++ How to Program
C++ How to Program (5th Edition)
ISBN: 0131857576
EAN: 2147483647
Year: 2004
Pages: 627

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net