Tuesday, October 20, 2009

13.3. Relationships Among Objects in an Inheritance Hierarchy



13.3. Relationships Among Objects in an
Inheritance Hierarchy


Section
12.4 created an employee class hierarchy, in which
class BasePlusCommissionEmployee inherited from class
CommissionEmployee. The Chapter
12 examples manipulated CommissionEmployee and
BasePlusCommissionEmployee objects by using
the objects' names to invoke their member functions. We now examine the
relationships among classes in a hierarchy more closely. The next several
sections present a series of examples that demonstrate how base-class and
derived-class pointers can be aimed at baseclass 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 baseclass 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 baseclass function as virtual. We then assign the address of a derived-class object to
the base-class pointer and use that pointer to invoke derived-class
functionality—precisely the capability we need to achieve polymorphic
behavior.


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


13.3.1. Invoking Base-Class
Functions from Derived-Class Objects


The example in Figs. 13.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 << "\nsocial security number: " << getSocialSecurityNumber()
90 << "\ngross sales: " << getGrossSales()
91 << "\ncommission 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 << "\nbase 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:\n\n";
37 commissionEmployee.print(); // invokes base-class print
38 cout << "\n\n";
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 << "\n\n\nCalling print with base-class pointer to "
44 << "\nbase-class object invokes base-class print function:\n\n";
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 << "\n\n\nCalling print with derived-class pointer to "
50 << "\nderived-class object invokes derived-class "
51 << "print function:\n\n";
52 basePlusCommissionEmployeePtr->print(); // invokes derived-class print
53
54 // aim base-class pointer at derived-class object and print
55 commissionEmployeePtr = &basePlusCommissionEmployee;
56 cout << "\n\n\nCalling print with base-class pointer to "
57 << "derived-class object\ninvokes base-class print "
58 << "function on that derived-class object:\n\n";
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.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_06\fig13_06.cpp 14: Cannot convert 'CommissionEmployee *'
to 'BasePlusCommissionEmployee *' in function main()


GNU C++ compiler error messages:


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


Microsoft Visual C++ 2005 compiler error messages:


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



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


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


Figure
13.7 shows the consequences of attempting to
invoke a derived-class member function off a base-class pointer. [Note: We are again using
classes CommissionEmployee and BasePlusCommissionEmployee of
Figs. 13.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_07\fig13_07.cpp 26: 'getBaseSalary' is not a member of
'CommissionEmployee' in function main()
Error E2316 Fig13_07\fig13_07.cpp 27: 'setBaseSalary' is not a member of
'CommissionEmployee' in function main()


Microsoft Visual C++ 2005 compiler error messages:


C:\cppfp_examples\ch13\Fig13_07\fig13_07.cpp(26) : error C2039:
'getBaseSalary' : is not a member of 'CommissionEmployee'
C:\cppfp_examples\ch13\Fig13_07\CommissionEmployee.h(10) :
see declaration of 'CommissionEmployee'
C:\cppfp_examples\ch13\Fig13_07\fig13_07.cpp(27) : error C2039:
'setBaseSalary' : is not a member of 'CommissionEmployee'
C:\cppfp_examples\ch13\Fig13_07\CommissionEmployee.h(10) :
see declaration of 'CommissionEmployee'


GNU C++ compiler error messages:



 

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




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



Software Engineering Observation 13.3








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



13.3.4. Virtual Functions


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


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


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


virtual void draw() const;


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



Software Engineering
Observation 13.4








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




Good Programming Practice 13.1








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 a program invokes a virtual
function through a baseclass 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 << "\nobjects with static binding\n\n";
37 commissionEmployee.print(); // static binding
38 cout << "\n\n";
39 basePlusCommissionEmployee.print(); // static binding
40
41 // output objects using dynamic binding
42 cout << "\n\n\nInvoking print function on base-class and "
43 << "derived-class \nobjects with dynamic binding";
44
45 // aim base-class pointer at base-class object and print
46 commissionEmployeePtr = &commissionEmployee;
47 cout << "\n\nCalling virtual function print with base-class pointer"
48 << "\nto base-class object invokes base-class "
49 << "print function:\n\n";
50 commissionEmployeePtr->print(); // invokes base-class print
51
52 // aim derived-class pointer at derived-class object and print
53 basePlusCommissionEmployeePtr = &basePlusCommissionEmployee;
54 cout << "\n\nCalling virtual function print with derived-class "
55 << "pointer\nto derived-class object invokes derived-class "
56 << "print function:\n\n";
57 basePlusCommissionEmployeePtr->print(); // invokes derived-class print
58
59 // aim base-class pointer at derived-class object and print
60 commissionEmployeePtr = &basePlusCommissionEmployee;
61 cout << "\n\nCalling virtual function print with base-class pointer"
62 << "\nto derived-class object invokes derived-class "
63 << "print function:\n\n";
64
65 // polymorphism; invokes BasePlusCommissionEmployee's print;
66 // base-class pointer to derived-class object
67 commissionEmployeePtr->print();
68 cout << endl;
69 return 0;
70 } // end main




 

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

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

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


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

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

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

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

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

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

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




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


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


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






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




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




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




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



Common Programming Error 13.1








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.





 


No comments:

Post a Comment