In Section 12.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 determined 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 custom print
function.
Software Engineering Observation 12.4
With virtual functions, the type of the object, not the type of the handle used to invoke the member function, determines which version of a virtual function to invoke.
First, we consider why virtual
functions are useful. Suppose that 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
, but 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 generally 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. This is polymorphic behavior.
To enable this 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’ve 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,
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’s invoked—virtual functions do not have to be const
functions.
Software Engineering Observation 12.5
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 12.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 class hierarchy to promote program clarity.
Software Engineering Observation 12.6
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()
) or a base-class reference to a derived-class object (e.g., shapeRef.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 or reference 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’s 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 pointers (and, as we’ll soon see, references).
Now let’s see how virtual
functions can enable polymorphic behavior in our employee hierarchy. Figures 12.4–12.5 are the headers for classes CommissionEmployee
and BasePlusCommissionEmployee
, respectively. We modified these to declare each class’s earnings
and print
member functions as virtual
(lines 29–30 of Fig. 12.4 and lines 19–20 of Fig. 12.5). Because functions earnings
and print
are virtual
in class CommissionEmployee
, class BasePlusCommissionEmployee
’s earnings
and print
functions override class CommissionEmployee
’s. In addition, class BasePlusCommissionEmployee
’s earnings
and print
functions are declared override.
1 // Fig. 12.4: CommissionEmployee.h
2 // CommissionEmployee class header declares earnings and print as virtual.
3 #ifndef COMMISSION_H
4 #define COMMISSION_H
5
6 #include <string> // C++ standard string class
7
8 class CommissionEmployee
9 {
10 public:
11 CommissionEmployee( const std::string &, const std::string &,
12 const std::string &, double = 0.0, double = 0.0 );
13
14 void setFirstName( const std::string & ); // set first name
15 std::string getFirstName() const; // return first name
16
17 void setLastName( const std::string & ); // set last name
18 std::string getLastName() const; // return last name
19
20 void setSocialSecurityNumber( const std::string & ); // set SSN
21 std::string getSocialSecurityNumber() const; // return SSN
22
23 void setGrossSales( double ); // set gross sales amount
24 double getGrossSales() const; // return gross sales amount
25
26 void setCommissionRate( double ); // set commission rate
27 double getCommissionRate() const; // return commission rate
28
29 virtual double earnings() const; // calculate earnings
30 virtual void print() const; // print object
31 private:
32 std::string firstName;
33 std::string lastName;
34 std::string socialSecurityNumber;
35 double grossSales; // gross weekly sales
36 double commissionRate; // commission percentage
37 }; // end class CommissionEmployee
38
39 #endif
1 // Fig. 12.5: 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 #include "CommissionEmployee.h" // CommissionEmployee class declaration
9
10 class BasePlusCommissionEmployee : public CommissionEmployee
11 {
12 public:
13 BasePlusCommissionEmployee( const std::string &, const std::string &,
14 const std::string &, double = 0.0, double = 0.0, double = 0.0 );
15
16 void setBaseSalary( double ); // set base salary
17 double getBaseSalary() const; // return base salary
18
19 virtual double earnings() const override; // calculate earnings
20 virtual void print() const override; // print object
21 private:
22 double baseSalary; // base salary
23 }; // end class BasePlusCommissionEmployee
24
25 #endif
Apply C++11’s override keyword to every overridden function in a derived-class. This forces the compiler to check whether the base class has a member function with the same name and parameter list (i.e., the same signature). If not, the compiler generates an error.
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 Figs. 11.14 and 11.15.
We modified Fig. 12.1 to create the program of Fig. 12.6. Lines 40–51 of Fig. 12.6 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 54 aims the base-class pointer commissionEmployeePtr
at derived-class object basePlusCommissionEmployee
. Note that when line 61 invokes member function print
off the base-class pointer, the derived-class BasePlusCommissionEmployee
’s print
member function is invoked, so line 61 outputs different text than line 53 does in Fig. 12.1 (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, class CommissionEmployee
’s print
function is invoked (Fig. 12.6, line 40), and when CommissionEmployeePtr
points to a BasePlusCommissionEmployee
object, class BasePlusCommissionEmployee
’s print
function is invoked (line 61). 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.
1 // Fig. 12.6: fig12_06.cpp
2 // Introducing polymorphism, virtual functions and dynamic binding.
3 #include <iostream>
4 #include <iomanip>
5 #include "CommissionEmployee.h"
6 #include "BasePlusCommissionEmployee.h"
7 using namespace std;
8
9 int main()
10 {
11 // create base-class object
12 CommissionEmployee commissionEmployee(
13 "Sue", "Jones", "222-22-2222", 10000, .06 );
14
15 // create base-class pointer
16 CommissionEmployee *commissionEmployeePtr = nullptr;
17
18 // create derived-class object
19 BasePlusCommissionEmployee basePlusCommissionEmployee(
20 "Bob", "Lewis", "333-33-3333", 5000, .04, 300 );
21
22 // create derived-class pointer
23 BasePlusCommissionEmployee *basePlusCommissionEmployeePtr = nullptr;
24
25 // set floating-point output formatting
26 cout << fixed << setprecision( 2 );
27
28 // output objects using static binding
29 cout << "Invoking print function on base-class and derived-class "
30 << "
objects with static binding
";
31 commissionEmployee.print(); // static binding
32 cout << "
";
33 basePlusCommissionEmployee.print(); // static binding
34
35 // output objects using dynamic binding
36 cout << "
Invoking print function on base-class and "
37 << "derived-class
objects with dynamic binding";
38
39 // aim base-class pointer at base-class object and print
40 commissionEmployeePtr = &commissionEmployee;
41 cout << "
Calling virtual function print with base-class pointer"
42 << "
to base-class object invokes base-class "
43 << "print function:
";
44 commissionEmployeePtr->print(); // invokes base-class print
45
46 // aim derived-class pointer at derived-class object and print
47 basePlusCommissionEmployeePtr = &basePlusCommissionEmployee;
48 cout << "
Calling virtual function print with derived-class "
49 << "pointer
to derived-class object invokes derived-class "
50 << "print function:
";
51 basePlusCommissionEmployeePtr->print(); // invokes derived-class print
52
53 // aim base-class pointer at derived-class object and print
54 commissionEmployeePtr = &basePlusCommissionEmployee;
55 cout << "
Calling virtual function print with base-class pointer"
56 << "
to derived-class object invokes derived-class "
57 << "print function:
";
58
59 // polymorphism; invokes BasePlusCommissionEmployee's print;
60 // base-class pointer to derived-class object
61 commissionEmployeePtr->print();
62 cout << endl;
63 } // end main
A problem can occur when using polymorphism to process dynamically allocated objects of a class hierarchy. So far you’ve seen destructors that are not declared with keyword virtual
. If a derived-class object with a non-virtual
destructor is destroyed by applying the delete
operator to a base-class pointer to the object, the C++ standard specifies that the behavior is undefined.
The simple solution to this problem is to create a public
virtual destructor in the base class. If a base class destructor is declared virtual
, the destructors of any derived classes are also virtual
and they override the base class destructor. For example, in class CommissionEmployee
’s definition, we can define the virtual
destructor as follows:
virtual ~CommissionEmployee() { }
Now, if an object in the hierarchy is destroyed explicitly by applying the delete
operator to a base-class pointer, the destructor for the appropriate class is called based on the object to which the base-class pointer points. Remember, when a derived-class object is destroyed, the base-class part of the derived-class object is also destroyed, so it’s important for the destructors of both the derived and base classes to execute. The base-class destructor automatically executes after the derived-class destructor. From this point forward, we’ll include a virtual
destructor in every class that contains virtual
functions.
Error-Prevention Tip 12.2
If a class has virtual functions, always provide a virtual destructor, even if one is not required for the class. This ensures that a custom derived-class destructor (if there is one) will be invoked when a derived-class object is deleted via a base class pointer.
Common Programming Error 12.1
Constructors cannot be virtual. Declaring a constructor virtual is a compilation error.
Prior to C++11, a derived class could override any of its base class’s virtual
functions. In C++11, a base-class virtual
function that’s declared final in its prototype, as in
virtual someFunction( parameters ) final;
cannot be overridden in any derived class—this guarantees that the base class’s final
member function definition will be used by all base-class objects and by all objects of the base class’s direct and indirect derived classes. Similarly, prior to C++11, any existing class could be used as a base class in a hierarchy. As of C++11, you can declare a class as final
to prevent it from being used as a base class, as in
class MyClass final // this class cannot be a base class
{
// class body
};
Attempting to override a final
member function or inherit from a final
base class results in a compilation error.