In Section 12.3.1, we aimed a base-class CommissionEmployee
pointer at a derived-class BasePlusCommissionEmployee
object, then invoked member function toString
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 toString
on the BasePlusCommissionEmployee
object, even though the pointer was aimed at a BasePlusCommissionEmployee
object that has its own custom toString
function.
virtual
Functions Are UsefulSuppose 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.
With virtual
functions, the type of the object—not the type of the handle used to invoke the object’s member function—determines which version of a virtual
function to invoke.
virtual
FunctionsTo 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 do 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’s invoked. Virtual functions do not have to be const
functions.
Once a function is declared virtual
, it remains virtual
all the way down the inheritance hierarchy from that point, even if that function is not explicitly declared virtual
when a derived class overrides it.
Even though certain functions are implicitly virtual
because of a declaration made higher in the class hierarchy, for clarity explicitly declare these functions virtual
at every level of the class hierarchy.
When a derived class chooses not to override a virtual
function from its base class, the derived class simply inherits its base class’s virtual
function implementation.
virtual
Function Through a Base-Class Pointer or ReferenceIf 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 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.
virtual
Function Through an Object’s NameWhen 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. Dynamic binding with virtual
functions occurs only off pointers and references.
virtual
Functions in the CommissionEmployee
HierarchyNow 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’ve modified these to declare each class’s earnings
and toString
member functions as virtual
(lines 28–29 of Fig. 12.4 and lines 17–18 of Fig. 12.5). Because functions earnings
and toString
are virtual
in class CommissionEmployee
, class BasePlusCommissionEmployee
’s earnings
and toString
functions override class CommissionEmployee
’s. In addition, class BasePlusCommissionEmployee
’s earnings
and toString
functions are declared with C++11’s override
keyword.
To help prevent errors, apply C++11’s override
keyword to the prototype of every derived-class function that overrides a base-class virtual
function. This enables the compiler to check whether the base class has a virtual
member function with the same signature. If not, the compiler generates an error. Not only does this ensure that you override the base-class function with the appropriate signature, it also prevents you from accidentally hiding a base-class function that has the same name and a different signature.
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 toString
, the BasePlusCommissionEmployee
object’s corresponding function will be invoked polymorphically. 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 32–44 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 47 aims the base-class pointer commissionEmployeePtr
at derived-class object basePlusCommissionEmployee
. When line 54 invokes member function toString
off the base-class pointer, the derived-class BasePlusCommissionEmployee
’s toString
member function is invoked, so line 54 outputs different text than line 46 does in Fig. 12.1 (when member function toString
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. When commissionEmployeePtr
points to a CommissionEmployee
object, class CommissionEmployee
’s toString
function is invoked (Fig. 12.6, line 36), and when commissionEmployeePtr
points to a BasePlusCommissionEmployee
object, class BasePlusCommissionEmployee
’s toString
function is invoked (line 54). Thus, the same toString
message—sent off a base-class pointer to a variety of derived-class objects—takes on many forms. This is polymorphic behavior.
virtual
DestructorsA 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
. For example, in class CommissionEmployee
’s definition (Fig. 12.4), 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 and requires a destructor.
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.
Constructors cannot be virtual
. Declaring a constructor virtual
is a compilation error.
The preceding destructor definition also may be written as follows:
virtual ~CommissionEmployee() = default;
In C++11, you can tell the compiler to explicitly generate the default version of a default constructor, copy constructor, move constructor, copy assignment operator, move assignment operator or destructor by following the special member function’s prototype with =
default
. This is useful, for example, when you explicitly define a constructor for a class and still want the compiler to generate a default constructor as well—in that case, add the following declaration to your class definition:
ClassName() = default;
final
Member Functions and ClassesPrior 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.