© Ivor Horton and Peter Van Weert 2018
Ivor Horton and Peter Van WeertBeginning C++17https://doi.org/10.1007/978-1-4842-3366-5_14

14. Polymorphism

Ivor Horton1  and Peter Van Weert2
(1)
Stratford-upon-Avon, Warwickshire, UK
(2)
Kessel-Lo, Belgium
 

Polymorphism is such a powerful feature of object-oriented programming that you’ll use it in the majority of your C++ programs. Polymorphism requires you to use derived classes, so the content of this chapter relies heavily on the concepts related to inheritance in derived classes that we introduced in the previous chapter.

In this chapter, you’ll learn:
  • What polymorphism is and how you get polymorphic behavior with your classes

  • What a virtual function is

  • What function overriding is and how this differs from function overloading

  • How default parameter values for virtual functions are used

  • When and why you need virtual destructors

  • How you cast between class types in a hierarchy

  • What a pure virtual function is

  • What an abstract class is

Understanding Polymorphism

Polymorphism is a capability provided by many object-oriented languages. In C++, polymorphism always involves the use of a pointer or a reference to an object to call a member function. Polymorphism only operates with classes that share a common base class. We’ll show how polymorphism works by considering an example with more boxes, but first we’ll explain the role of a pointer to a base class because it’s fundamental to the process.

Using a Base Class Pointer

In the previous chapter, you saw how an object of a derived class type contains a subobject of the base class type. In other words, you can regard every derived class object as a base class object. Because of this, you can always use a pointer to a base class to store the address of a derived class object; in fact, you can use a pointer to any direct or indirect base class to store the address of a derived class object. Figure 14-1 shows how the Carton class is derived from the Box base class by single inheritance, and the CerealPack class is derived by multiple inheritances from the Carton and Contents base classes. It illustrates how pointers to base classes can be used to store addresses of derived class objects.
../images/326945_5_En_14_Chapter/326945_5_En_14_Fig1_HTML.gif
Figure 14-1.

Storing the address of a derived class object in a base class pointer

The reverse is not true. For instance, you can’t use a pointer of type Carton* to store the address of an object of type Box. This is logical because a pointer type incorporates the type of object to which it can point. A derived class object is a specialization of its base—it is a base class object—so using a pointer to the base to store its address is reasonable. However, a base class object is definitely not a derived class object, so a pointer to a derived class type cannot point to it. A derived class always contains a complete subobject of each of its bases, but each base class represents only part of a derived class object.

We’ll look at a specific example. Suppose you derive two classes from the Box class to represent different kinds of containers, Carton and ToughPack. Suppose further that the volume of each of these derived types is calculated differently. For a Carton made of cardboard, you might just reduce the volume slightly to take the thickness of the material into account. For a ToughPack object, you might have to reduce the usable volume by a considerable amount to allow for protective packaging. The Carton class definition could be of the following form:

class Carton : public Box
{
  // Details of the class...
public:
  double volume() const;
};

The ToughPack class could have a similar definition:

class ToughPack : public Box
{
  // Details of the class...
public:
  double volume() const;
};

Given these class definitions (the function definitions follow later), you can declare and initialize a pointer as follows:

Carton carton {10.0, 10.0, 5.0};
Box* pBox {&carton};

The pointer pBox, of type pointer to Box, has been initialized with the address of carton. This is possible because Carton is derived from Box and therefore contains a subobject of type Box. You could use the same pointer to store the address of a ToughPack object because the ToughPack class is also derived from Box:

ToughPack hardcase {12.0, 8.0, 4.0};
pBox = &hardcase;

The pBox pointer can contain the address of any object of any class that has Box as a base. The type of the pointer, Box*, is called its static type. Because pBox is a pointer to a base class, it also has a dynamic type, which varies according to the type of object to which it points. When pBox is pointing to a Carton object, its dynamic type is a pointer to Carton. When pBox is pointing to a ToughPack object, its dynamic type is a pointer to ToughPack. When pBox points to an object of type Box, its dynamic type is the same as its static type. The magic of polymorphism springs from this. Under conditions that we’ll explain shortly, you can use the pBox pointer to call a function that’s defined both in the base class and in each derived class and have the function that is actually called selected at runtime on the basis of the dynamic type of pBox. Consider these statements:

double vol {};
vol = pBox->volume();                       // Store volume of the object pointed to

If pBox contains the address of a Carton object, then this statement calls volume() for the Carton object. If it points to a ToughPack object, then this statement calls volume() for ToughPack. This works for any classes derived from Box, provided of course the aforementioned conditions are met. If they are, the expression pBox->volume() can result in different behavior depending on what pBox is pointing to. Perhaps more importantly, the behavior that is appropriate to the object pointed to by pBox is selected automatically at runtime.

Polymorphism is a powerful mechanism. Situations arise frequently in which the specific type of an object cannot be determined in advance—not at design time or at compile time. Situations, in other words, in which the type can be determined only at runtime. This can be handled easily using polymorphism. Polymorphism is commonly used with interactive applications, where the type of input is up to the whim of the user. For instance, a graphics application that allows different shapes to be drawn—circles, lines, curves, and so on—may define a derived class for each shape type, and these classes all have a common base class called Shape. A program can store the address of an object the user creates in a pointer, pShape, of type Shape* and draw the shape with a statement such as pShape->draw(). This will call the draw() function for the shape that is pointed to, so this one expression can draw any kind of shape. Let’s take a more in-depth look at how inherited functions behave.

Calling Inherited Functions

Before we get to the specifics of polymorphism, we need to explain the behavior of inherited member functions a bit further. To help with this, we’ll revise the Box class to include a function that calculates the volume of a Box object, and another function that displays the resulting volume. The new version of the class definition in Box.h and Box.cpp will be as follows:

// Box.h
#ifndef BOX_H
#define BOX_H
#include <iostream>
class Box
{
protected:
  double length {1.0};
  double width {1.0};
  double height {1.0};
public:
  Box() = default;
  Box(double lv, double wv, double hv) : length {lv}, width {wv}, height {hv} {}
  // Function to show the volume of an object
  void showVolume() const
  { std::cout << "Box usable volume is " << volume() << std::endl; }
  // Function to calculate the volume of a Box object
  double volume() const { return length * width * height; }
};
#endif

We can display the usable volume of a Box object by calling the showVolume() function for the object. The member variables are specified as protected, so they can be accessed by the member functions of any derived class.

We’ll also define the ToughPack class with Box as a base. A ToughPack object incorporates packing material to protect its contents, so its capacity is only 85 percent of a basic Box object. Therefore, a different volume() function is needed in the derived class to account for this:

// ToughPack.h
#ifndef TOUGHPACK_H
#define TOUGHPACK_H
#include "Box.h"
class ToughPack : public Box
{
public:
  // Constructor
  ToughPack(double lv, double wv, double hv) : Box {lv, wv, hv} {}
  // Function to calculate volume of a ToughPack allowing 15% for packing
  double volume() const { return 0.85 * length * width * height; }
};
#endif

Conceivably, you could have additional members in this derived class, but for the moment, we’ll keep it simple, concentrating on how the inherited functions work. The derived class constructor just calls the base class constructor in its member initializer list to set the member variable values. You don’t need any statements in the body of the derived class constructor. You also have a new version of the volume() function to replace the version from the base class. The idea here is that you can get the inherited function showVolume() to call the derived class version of volume() when you call it for an object of the ToughPack class. Let’s see whether it works:

// Ex14_01.cpp
// Behavior of inherited functions in a derived class
#include "Box.h"                           // For the Box class
#include "ToughPack.h"                     // For the ToughPack class
int main()
{
  Box box {20.0, 30.0, 40.0};              // Define a box
  ToughPack hardcase {20.0, 30.0, 40.0};   // Declare tough box - same size
  box.showVolume();                        // Display volume of base box
  hardcase.showVolume();                   // Display volume of derived box
}

When we run the program, we get this rather disappointing output:

Box usable volume is 24000
Box usable volume is 24000

The derived class object is supposed to have a smaller capacity than the base class object, so the program is obviously not working as intended. Let’s try to establish what’s going wrong. The second call to showVolume() in main() is for an object of the derived class, ToughPack, but evidently this is not being taken into account. The volume of a ToughPack object should be 85 percent of that of a basic Box object with the same dimensions.

The trouble is that when the volume() function is called by the showVolume() function, the compiler sets it once and for all as the version of volume() defined in the base class. No matter how you call showVolume(), it will never call the ToughPack version of the volume() function. When function calls are fixed in this way before the program is executed, it is called static resolution of the function call, or static binding. The term early binding is also commonly used. In this example, a particular volume() function is bound to the call from the function showVolume() when the program is compiled and linked. Every time showVolume() is called, it uses the base class volume() function that’s bound to it.

Note

The same kind of resolution would occur in the derived class ToughPack. If you add a showVolume() function that calls volume() to the ToughPack class, the volume() call resolves statically to the derived class function.

What if you call the volume() function for the ToughPack object directly? As a further experiment, let’s add statements in main() to call the volume() function of a ToughPack object directly and also through a pointer to the base class:

std::cout << "hardcase volume is " << hardcase.volume() << std::endl;
Box *pBox {&hardcase};
std::cout << "hardcase volume through pBox is " << pBox->volume() << std::endl;

Place these statements at the end of main(). Now when you run the program, you’ll get this output:

Box usable volume is 24000
Box usable volume is 24000
hardcase volume is 20400
hardcase volume through pBox is 24000

This is quite informative. You can see that a call to volume() for the derived class object, hardcase, calls the derived class volume() function, which is what you want. The call through the base class pointer pBox, however, is resolved to the base class version of volume(), even though pBox contains the address of hardcase. In other words, both calls are resolved statically. The compiler implements these calls as follows:

std::cout << "hardcase volume is " << hardcase.ToughPack::volume() << std::endl;
Box *pBox {&hardcase};
std::cout << "hardcase volume through pBox is " << pBox->Box::volume() << std::endl;

A static function call through a pointer is determined solely by the pointer type and not by the object to which it points. In other words, it is determined by the static type rather than the dynamic type. The pointer pBox has a static type pointer to Box, so any static call using pBox can only call a member function of Box.

Note

Any call to a function through a base class pointer that is resolved statically calls a base class function.

What we want is for the volume() function that is to be called in any given instance to be resolved when the program executes. So, if showVolume() is called for a derived class object, we want the derived class volume() function to be called, not the base class version. When the volume() function is called through a base class pointer, we want the volume() function that is appropriate to the object pointed to in order to be called. This sort of operation is referred to as dynamic binding or late binding. To make this work, we have to tell the compiler that the volume() function in Box and any overrides in the classes derived from Box are special, and calls to them are to be resolved dynamically. We can obtain this effect by specifying that volume() in the base class is a virtual function, which will result in a virtual function call for volume().

Virtual Functions

When you specify a function as virtual in a base class, you indicate to the compiler that you want dynamic binding for function calls in any class that’s derived from this base class. A virtual function is declared in a base class by using the keyword virtual, as shown in Figure 14-2. Describing a class as polymorphic means that it is a derived class that contains at least one virtual function.
../images/326945_5_En_14_Chapter/326945_5_En_14_Fig2_HTML.gif
Figure 14-2.

Calling a virtual function

A function that you specify as virtual in a base class will be virtual in all classes that are directly or indirectly derived from the base. This is the case whether or not you specify the function as virtual in a derived class. To obtain polymorphic behavior, each derived class may implement its own version of the virtual function (although it’s not obliged to—we’ll look into that later). You make virtual function calls using a variable whose type is a pointer or a reference to a base class object. Figure 14-2 illustrates how a call to a virtual function through a pointer is resolved dynamically. The pointer to the base class type is used to store the address of an object with a type corresponding to one of the derived classes. It could point to an object of any of the three derived classes shown or, of course, to a base class object. The type of the object to which the pointer points when the call executes determines which volume() function is called.

Note that a call to a virtual function using an object is always resolved statically. You only get dynamic resolution of calls to virtual functions through a pointer or a reference. Storing an object of a derived class type in a variable of a base type will result in the derived class object being sliced, so it has no derived class characteristics. With that said, let’s give virtual functions a whirl. To make the previous example work as it should, a very small change to the Box class is required. We just need to add the virtual keyword to the definition of the volume() function:

class Box
{
  // Rest of the class as before...
public:
  // Function to calculate the volume of a Box object
  virtual double volume() const { return length * width * height; }
};

Caution

If a member function definition is outside the class definition, you must not add the virtual keyword to the function definition; it would be an error to do so. You can only add virtual to declarations or definitions inside a class definition.

To make it more interesting, let’s implement the volume() function in a new class called Carton a little differently. Here is the class definition:

// Carton.h
#ifndef CARTON_H
#define CARTON_H
#include <string>
#include <string_view>
#include "Box.h"
class Carton : public Box
{
private:
  std::string material;
public:
  // Constructor explicitly calling the base constructor
  Carton(double lv, double wv, double hv, std::string_view str="cardboard")
         : Box{lv,wv,hv}, material{str}
  {}
  // Function to calculate the volume of a Carton object
  double volume() const
  {
    const double vol {(length - 0.5)*(width - 0.5)*(height - 0.5)};
    return vol > 0.0? vol : 0.0;
  }
};
#endif

The volume() function for a Carton object assumes the thickness of the material is 0.25, so 0.5 is subtracted from each dimension to account for the sides of the carton. If a Carton object has been created with any of its dimensions less than 0.5 for some reason, then this will result in a negative value for the volume, so in such a case, the carton’s volume will be set to zero.

We’ll also use the ToughPack class from Ex14_01. Here’s the code for the source file containing main():

// Ex14_02.cpp
// Using virtual functions
#include <iostream>
#include "Box.h"                                 // For the Box class
#include "ToughPack.h"                           // For the ToughPack class
#include "Carton.h"                              // For the Carton class
int main()
{
  Box box {20.0, 30.0, 40.0};
  ToughPack hardcase {20.0, 30.0, 40.0};         // A derived box - same size
  Carton carton {20.0, 30.0, 40.0, "plastic"};   // A different derived box
  box.showVolume();                              // Volume of Box
  hardcase.showVolume();                         // Volume of ToughPack
  carton.showVolume();                           // Volume of Carton
  // Now using a base pointer...
  Box* pBox {&box};                              // Points to type Box
  std::cout << "\nbox volume through pBox is " << pBox->volume() << std::endl;
  pBox->showVolume();
  pBox = &hardcase;                             // Points to type ToughPack
  std::cout << "hardcase volume through pBox is " << pBox->volume() << std::endl;
  pBox->showVolume();
  pBox = &carton;                              // Points to type Carton
  std::cout << "carton volume through pBox is " << pBox->volume() << std::endl;
  pBox->showVolume();
}

The output that is produced should be as follows:

Box usable volume is 24000
Box usable volume is 20400
Box usable volume is 22722.4
box volume through pBox is 24000
Box usable volume is 24000
hardcase volume through pBox is 20400
Box usable volume is 20400
carton volume through pBox is 22722.4
Box usable volume is 22722.4

Notice that we have not added the virtual keyword to the volume() functions of either the Carton or ToughPack class. The virtual keyword applied to the function volume() in the base class is sufficient to determine that all definitions of the function in derived classes will also be virtual. You can optionally use the virtual keyword for your derived class functions as well, as illustrated in Figure 14-2. Whether or not you do is a matter of personal preference. We'll return to this choice later in this chapter.

The program is now clearly doing what we wanted. The call to showVolume() for the box object calls the base class version of volume() because box is of type Box. The next call to showVolume() is for the ToughPack object hardcase. It calls the showVolume() function inherited from the Box class, but the call to volume() in showVolume() is resolved to the version defined in the ToughPack class because volume() is a virtual function. Therefore, you get the volume calculated appropriately for a ToughPack object. The third call of showVolume() for the carton object calls the Carton class version of volume(), so you get the correct result for that too.

Next, you use the pointer pBox to call the volume() function directly and also indirectly through the nonvirtual showVolume() function. The pointer first contains the address of the Box object box and then the addresses of the two derived class objects in turn. The resulting output for each object shows that the appropriate version of the volume() function is selected automatically in each case, so you have a clear demonstration of polymorphism in action.

Requirements for Virtual Function Operation

For a function to behave “virtually,” its definition in a derived class must have the same signature as it has in the base class. If the base class function is const, for instance, then the derived class function must therefore also be const. Generally, the return type of a virtual function in a derived class must be the same as that in the base class as well, but there’s an exception when the return type in the base class is a pointer or a reference to a class type. In this case, the derived class version of a virtual function may return a pointer or a reference to a more specialized type than that of the base. We won’t be going into this further, but in case you come across it elsewhere, the technical term used in relation to these return types is covariance.

If the function name and parameter list of a function in a derived class are the same as those of a virtual function declared in the base class, then the return type must be consistent with the rules for a virtual function. If it isn’t, the derived class function won’t compile. Another restriction is that a virtual function can’t be a template function.

In standard object-oriented programming terms, a function in a derived class that redefines a virtual function of the base class is said to override this function. A function with the same name as a virtual function in a base class only overrides that function if the remainder of their signatures match exactly as well; if they do not, the function in the derived class is a new function that hides the one in the base class. The latter is what we saw in the previous chapter when we discussed duplicate member function names.

This of course implies that if you try to use different parameters for a virtual function in a derived class or use different const specifiers, then the virtual function mechanism won’t work. The function in the derived class then defines a new, different function—and this new function will therefore operate with static binding that is established and fixed at compile time.

You can test this by deleting the const keyword from the definition of volume() in the Carton class and running Ex14_02 again. The volume() function signature in Carton no longer matches the virtual function in Box, so the derived class volume() function is not virtual. Consequently, the resolution is static so that the function called for Carton objects through a base pointer, or even indirectly through the showVolume() function, is the base class version.

Note

static member functions cannot be virtual. As their name suggest, calls of static functions are always resolved statically. Even if you call a static member function on a polymorphic object, the member function is resolved using the static type of the object. This gives us yet another reason to always call static member functions by prefixing them with the class name instead of that of an object. That is, always use MyClass::myStaticFunction() instead of myObject.myStaticFunction(). This makes it crystal clear not to expect polymorphism.

Using override

It’s easy to make a mistake in the specification of a virtual function in a derived class. If you define Volume()—note the capital V—in a class derived from Box, it will not be virtual because the virtual function in the base class is volume(). This means that calls to Volume() will be resolved statically, and the virtual volume() function in the class will be inherited from the base class. The code may still compile and execute but not correctly. Similarly, if you define a volume() function in a derived class but forget to specify const, this function will overload instead of override the base class function. These kinds of errors can be difficult to spot. You can protect against such errors by using the override specifier for every virtual function declaration in a derived class, like this:

class Carton : public Box
{
  // Details of the class as in Ex14_02...
public:
  double volume() const override
  {
    // Function body as before...
  }
};

The override specification, like the virtual one, only appears within the class definition. It must not be applied to an external definition of a member function. The override specification causes the compiler to verify that the base class declares a class member that is virtual and has the same signature. If it doesn’t, the compiler flags the definition here as an error (give it a try!).

Tip

Always add an override specification to the declaration of a virtual function override. First, this guarantees that you have not made any mistakes in the function signatures at the time of writing. Second, and perhaps even more important, it safeguards you and your team from forgetting to change any existing function overrides if the signature of the base class function needs to be changed.

If you add the override keyword to every function declaration that overrides a base class’s virtual function, some argue it’s clear already to anyone reading it that this is a virtual function and that there’s therefore no need to apply the virtual keyword in derived classes. Other style guides insist to always add virtual nonetheless because it makes it even more apparent that it concerns a virtual function. There is no right answer. In this book, we’ll limit the use of the virtual keyword to base class functions and apply the override specification to all virtual function overrides in derived classes. But you should feel free to include the virtual keyword to function overrides as well if you feel it helps.

Using final

Sometimes you may want to prevent a member function from being overridden in a derived class. This could be because you want to limit how a derived class can modify the behavior of the class interface, for example. You can do this by specifying that a function is final. You could prevent the volume() function in the Carton class from being overridden by definitions in classes derived from Carton by specifying it like this:

class Carton : public Box
{
  // Details of the class as in Ex14_02...
public:
  double volume() const override final
  {
    // Function body as before...
  }
};

Attempts to override volume() in classes that have Carton as a base will result in a compiler error. This ensures that only the Carton version can be used for derived class objects. The order in which you put the override and final keywords does not matter—so both override final and final override are correct—but both have to come after const or any other part of the function signature.

Note

In principle you could declare a member function that is both virtual and final even if it does not override any base class member. This would be self-contradictory, though. You add virtual to allow function overrides, and you add final to prevent them. Note that there is no contradiction in combining override and final. This just states that you disallow any further overrides of the function you are overriding.

You can also specify an entire class as final, like this:

class Carton final : public Box
{
  // Details of the class as in Ex14_02...
public:
  double volume() const override
  {
    // Function body as before...
  }
};

Now the compiler will not allow Carton to be used as a base class. No further derivation from the Carton class is possible. Note that this time it is perfectly sensible to use final on a class that does not have any base class of its own. What does not make sense, though, is to introduce new virtual functions in a final class, that is, virtual functions that do not override a base class function.

Note

final and override are not keywords because making them keywords could break code that was written before they were introduced. This means you could use final and override as variable or even class names in your code. This doesn’t mean you should, though; it only creates confusion.

Virtual Functions and Class Hierarchies

If you want your function to be treated as virtual when it is called using a base class pointer, then you must declare it as virtual in the base class. You can have as many virtual functions as you want in a base class, but not all virtual functions need to be declared within the most basic base class in a hierarchy. This is illustrated in Figure 14-3.
../images/326945_5_En_14_Chapter/326945_5_En_14_Fig3_HTML.gif
Figure 14-3.

Virtual functions in a hierarchy

When you specify a function as virtual in a class, the function is virtual in all classes derived directly or indirectly from that class. All of the classes derived from the Box class in Figure 14-3 inherit the virtual nature of the volume() function, even if they do not repeat the virtual keyword. You can call volume() for objects of any of these class types through a pointer of type Box* because the pointer can contain the address of an object of any class in the hierarchy.

The Crate class doesn’t define volume(), so the version inherited from Carton would be called for Crate objects. It is inherited as a virtual function and therefore can be called polymorphically.

A pointer pCarton, of type Carton*, could also be used to call volume(), but only for objects of the Carton class and the two classes that have Carton as a base: Crate and Packet.

The Carton class and the classes derived from it also contain the virtual function doThat() . This function can also be called polymorphically using a pointer of type Carton*. Of course, you cannot call doThat() for these classes using a pointer of type Box* because the Box class doesn’t define the function doThat().

Similarly, the virtual function doThis() could be called for objects of type ToughPack, BigPack, and TinyPack using a pointer of type ToughPack*. Of course, the same pointer could also be used to call the volume() function for objects of these class types.

Access Specifiers and Virtual Functions

The access specification of a virtual function in a derived class can be different from the specification in the base class. When you call the virtual function through a base class pointer, the access specification in the base class determines whether the function is accessible, regardless of the type of object pointed to. If the virtual function is public in the base class, it can be called for any derived class through a pointer (or a reference) to the base class, regardless of the access specification in the derived class. We can demonstrate this by modifying the previous example. Modify the ToughPack class definition from Ex14_02 to make the volume() function protected, and add the override keyword to its declaration to make absolutely sure it indeed overrides a virtual function from the base class:

class ToughPack : public Box
{
public:
  // Constructor
  ToughPack(double lv, double wv, double hv) : Box {lv, wv, hv} {}
protected:
  // Function to calculate volume of a ToughPack allowing 15% for packing
  double volume() const override { return 0.85 * length * width * height; }
};

The main() function changes very slightly with a commented-out statement added:

// Ex14_03.cpp
// Access specifiers and virtual functions
#include <iostream>
#include "Box.h"                                 // For the Box class
#include "ToughPack.h"                           // For the ToughPack class
#include "Carton.h"                              // For the Carton class
int main()
{
  Box box {20.0, 30.0, 40.0};
  ToughPack hardcase {20.0, 30.0, 40.0};         // A derived box - same size
  Carton carton {20.0, 30.0, 40.0, "plastic"};   // A different derived box
  box.showVolume();                              // Volume of Box
  hardcase.showVolume();                         // Volume of ToughPack
  carton.showVolume();                           // Volume of Carton
// Uncomment the following statement for an error
// std::cout << "hardcase volume is " << hardcase.volume() << std::endl;
  // Now using a base pointer...
  Box* pBox {&box};                              // Points to type Box
  std::cout << "\nbox volume through pBox is " << pBox->volume() << std::endl;
  pBox->showVolume();
  pBox = &hardcase;                              // Points to type ToughPack
  std::cout << "hardcase volume through pBox is " << pBox->volume() << std::endl;
  pBox->showVolume();
  pBox = &carton;                                // Points to type Carton
  std::cout << "carton volume through pBox is " << pBox->volume() << std::endl;
  pBox->showVolume();
}

It should come as no surprise that this code otherwise produces the same output as the last example. Even though volume() is declared as protected in the ToughPack class, you can still call it for the hardcase object through the showVolume() function that is inherited from the Box class. You can also call it directly through a pointer to the base class, pBox. However, if you uncomment the line that calls the volume() function directly using the hardcase object, the code won’t compile.

What matters here is whether the call is resolved dynamically or statically. When you use a class object, the call is determined statically by the compiler. Calling volume() for a ToughPack object calls the function defined in that class. Because the volume() function is protected in ToughPack, the call for the hardcase object won’t compile. All the other calls are resolved when the program executes; they are polymorphic calls. In this case, the access specification for a virtual function in the base class is inherited in all the derived classes. This is regardless of the explicit specification in the derived class; the explicit specification only affects calls that are resolved statically.

So, access specifiers determine whether a function can be called based on the static type of an object. The consequence is that changing the access specifier of a function override to a more restricted one than that of the base class function is somewhat futile. This access restriction can easily be bypassed by using a pointer to the base class. This is shown by the showVolume() function of ToughPack in Ex14_03.

Tip

A function’s access specifier determines whether you can call that function; it plays no role whatsoever, though, in determining whether you can override it. The consequence is that you can override a private virtual function of a given base class. In fact, it is often recommended that you declare your virtual functions private.

In a way, private virtual functions give you the best of two worlds. On one hand, the function is private, meaning it cannot be called from outside your class. On the other hand, the function is virtual, allowing derived classes to override and customize its behavior. In other words, even though you facilitate polymorphism, you are still in perfect control where and when such a private virtual member function is called. This function could be a single step in a more complex algorithm, a step that is to be executed only once all previous steps of the algorithm have already been correctly performed. Or it could be a function that must to be called only after acquiring a particular resource, for instance after performing the necessary thread synchronization.

The fundamental idea behind this is the same as with data hiding. The more you restrict access to members, the easier it becomes to ensure that they aren’t used incorrectly. Some classic object-oriented design patterns—most prominently the so-called template method pattern—are best implemented using private virtual functions. These patterns are a bit too advanced for us too go into in more detail here. Just understand that access specifiers and overriding are two orthogonal concepts, and always keep in mind that declaring your virtual functions private is a viable option.

Default Argument Values in Virtual Functions

Default argument values are dealt with at compile time, so you can get unexpected results when you use default argument values with virtual function parameters. If the base class declaration of a virtual function has a default argument value and you call the function through a base pointer, you’ll always get the default argument value from the base class version of the function. Any default argument values in derived class versions of the function will have no effect. We can demonstrate this quickly by altering the previous example to include a parameter with a default argument value for the volume() function in all three classes. Change the definition of the volume() function in the Box class to the following:

  virtual double volume(int i=5) const
  {
    std::cout << "Box parameter = " << i << std::endl;
    return length * width * height;
  }

In the Carton class it should be as follows:

  double volume(int i = 50) const override
  {
    std::cout << "Carton parameter = " << i << std::endl;
    double vol {(length - 0.5)*(width - 0.5)*(height - 0.5)};
    return vol > 0.0 ? vol : 0.0;
  }

Finally, in the ToughPack class, you can define volume() as follows and make it public once more:

public:
  double volume(int i = 500) const override
  {
    std::cout << "ToughPack parameter = " << i << std::endl;
    return 0.85 * length * width * height;
  }

Obviously, the parameter serves no purpose here other than to demonstrate how default values are assigned.

Once you’ve made these changes to the class definitions, you can try the default parameter values with the main() function from the previous example, in which you uncomment the line that calls the volume() member for the hardcase object directly. The complete program is in the download as Ex14_04. You’ll get this output:

Box parameter = 5
Box usable volume is 24000
ToughPack parameter = 5
Box usable volume is 20400
Carton parameter = 5
Box usable volume is 22722.4
ToughPack parameter = 500
hardcase volume is 20400
Box parameter = 5
box volume through pBox is 24000
Box parameter = 5
Box usable volume is 24000
ToughPack parameter = 5
hardcase volume through pBox is 20400
ToughPack parameter = 5
Box usable volume is 20400
Carton parameter = 5
carton volume through pBox is 22722.4
Carton parameter = 5
Box usable volume is 22722.4

In every instance of when volume() is called except one, the default parameter value output is that specified for the base class function. The exception is when you call volume() using the hardcase object. This is resolved statically to volume() in the ToughPack class, so the default parameter value specified in the ToughPack class is used. All the other calls are resolved dynamically, so the default parameter value specified in the base class applies, even though the function executing is in a derived class.

Using References to Call Virtual Functions

You can call a virtual function through a reference; reference parameters are particularly powerful tools for applying polymorphism, particularly when calling functions that use pass-by-reference. You can pass a base class object or any derived class object to a function with a parameter that’s a reference to the base class. You can use the reference parameter within the function body to call a virtual function in the base class and get polymorphic behavior. When the function executes, the virtual function for the object that was passed as the argument is selected automatically at runtime. We can show this in action by modifying Ex14_02 to call a function that has a parameter of type reference to Box:

// Ex14_05.cpp
// Using a reference parameter to call virtual function
#include <iostream>
#include "Box.h"                                 // For the Box class
#include "ToughPack.h"                           // For the ToughPack class
#include "Carton.h"                              // For the Carton class
// Global function to display the volume of a box
void showVolume(const Box& rBox)
{
  std::cout << "Box usable volume is " << rBox.volume() << std::endl;
}
int main()
{
  Box box {20.0, 30.0, 40.0};                    // A base box
  ToughPack hardcase {20.0, 30.0, 40.0};         // A derived box - same size
  Carton carton {20.0, 30.0, 40.0, "plastic"};   // A different derived box
  showVolume(box);                               // Display volume of base box
  showVolume(hardcase);                          // Display volume of derived box
  showVolume(carton);                            // Display volume of derived box
}

Running this program should produce this output:

Box usable volume is 24000
Box usable volume is 20400
Box usable volume is 22722.4

The class definitions are the same as in Ex14_02. There’s a new global function that calls volume() using its reference parameter to call the volume() member of an object. main() defines the same objects as in Ex14_02 but calls the global showVolume() function with each of the objects to output their volumes. As you see from the output, the correct volume() function is being used in each case, confirming that polymorphism works through a reference parameter.

Each time the showVolume() function is called, the reference parameter is initialized with the object that is passed as an argument. Because the parameter is a reference to a base class, the compiler arranges for dynamic binding to the virtual volume() function.

Polymorphic Collections

Polymorphism becomes particularly interesting when working with so-called polymorphic or heterogeneous collections of objects—both fancy names for collections of base class pointers that contain objects with different dynamic types. Examples of collections include plain C-style arrays, but also the more modern and powerful std::array<> and std::vector<> templates from the Standard Library.

We’ll demonstrate this concept using the Box, Carton, and ToughPack classes from Ex14_03 and a revised main() function:

// Ex14_06.cpp
// Polymorphic vectors of smart pointers
#include <iostream>
#include <memory>                                // For smart pointers
#include <vector>                                // For vector
#include "Box.h"                                 // For the Box class
#include "ToughPack.h"                           // For the ToughPack class
#include "Carton.h"                              // For the Carton class
int main()
{
  // Careful: this first attempt at a mixed collection is a bad idea (object slicing!)
  std::vector<Box> boxes;
  boxes.push_back(Box{20.0, 30.0, 40.0});
  boxes.push_back(ToughPack{20.0, 30.0, 40.0});
  boxes.push_back(Carton{20.0, 30.0, 40.0, "plastic"});
  for (const auto& p : boxes)
    p.showVolume();
  std::cout << std::endl;
  // Next, we create a proper polymorphic vector<>:
  std::vector<std::unique_ptr<Box>> polymorphicBoxes;
  polymorphicBoxes.push_back(std::make_unique<Box>(20.0, 30.0, 40.0));
  polymorphicBoxes.push_back(std::make_unique<ToughPack>(20.0, 30.0, 40.0));
  polymorphicBoxes.push_back(std::make_unique<Carton>(20.0, 30.0, 40.0, "plastic"));
  for (const auto& p : polymorphicBoxes)
    p->showVolume();
}

The output from this example is as follows:

Box usable volume is 24000
Box usable volume is 24000
Box usable volume is 24000
Box usable volume is 24000
Box usable volume is 20400
Box usable volume is 22722.4

The first part of the program shows how not to create a polymorphic collection. If you assign objects of derived classes in a vector<> of base class objects by value, as always, object slicing will occur. That is, only the subobject corresponding to that base class is retained. The vector in general has no room to store the full object. The dynamic type of the object also gets converted into that of the base class. If you want polymorphism, you know you must always work with either pointers or references.

For our proper polymorphic vector in the second part of the program we could’ve used a vector<> of plain Box* pointers—that is, a vector of type std::vector<Box*>—and store pointers to dynamically allocated Box, ToughPack, and Carton objects in there. The downside of that would’ve been that we’d have had to remember to also delete these Box objects at the end of the program.

You already know that the Standard Library offers so-called smart pointers to help with this. Smart pointers allow us to work safely with pointers without having to worry all the time about deleting the objects.

In the polymorphicBoxes vector, we therefore store elements of type std::unique_ptr<Box>, which are smart pointers to Box objects. The elements can store addresses for objects of Box or any class derived from Box, so there’s an exact parallel with the raw pointers you have seen up to now. Fortunately, as the output shows, polymorphism remains alive and well with smart pointers. When you are creating objects in the free store, smart pointers still give you polymorphic behavior while also removing any potential for memory leaks.

Tip

To obtain memory-safe polymorphic collections of objects, you can store standard smart pointers such as std::unique_ptr<> and shared_ptr<> inside standard containers such as std::vector<> and array<>.

Destroying Objects Through a Pointer

The use of pointers to a base class when you are working with derived class objects is very common because that’s how you can take advantage of virtual functions. If you use pointers or smart pointers to objects created in the free store, a problem can arise when derived class objects are destroyed. You can see the problem if you add destructors to the various Box classes that display a message. Start from the files from Ex14_06, and add a destructor to the Box base class that just displays a message when it gets called:

class Box
{
protected:
  double length {1.0};
  double width {1.0};
  double height {1.0};
public:
  Box() = default;
  Box(double lv, double wv, double hv) : length {lv}, width {wv}, height {hv} {}
  ∼Box() { std::cout << "Box destructor called" << std::endl; }
  // Remainder of the Box class as before...
};

Do the same for the ToughPack and Carton classes. That is, add destructors of the following form:

  ∼ToughPack() { std::cout << "ToughPack destructor called" << std::endl; }

and

  ∼Carton() { std::cout << "Carton destructor called" << std::endl; }

You’ll need to include the <iostream> header into files where it’s not already included. There is no need to change the main() function. The complete program is present in the code download as Ex14_07. It produces output that ends with the following dozen or so lines (the output that you’ll see before this corresponds to the various Box elements being pushed and sliced into the first vector<>; but this is not the part we want to dissect here):

...
Box usable volume is 24000
Box usable volume is 24000
Box usable volume is 24000
Box usable volume is 24000
Box usable volume is 20400
Box usable volume is 22722.4
Box destructor called
Box destructor called
Box destructor called
Box destructor called
Box destructor called
Box destructor called

Clearly we have a failure on our hands. The same base class destructor is called for all six objects, even though four of them are objects of a derived class. This occurs even for the objects stored in the polymorphic vector. Naturally, the cause of this behavior is that the destructor function is resolved statically instead of dynamically, just like with any other function. To ensure that the correct destructor is called for a derived class, we need dynamic binding for the destructors. What we need is virtual destructor functions.

Caution

You might think that for objects of classes such as ToughPack or Carton calling the wrong destructor is no big deal because their destructors are basically empty. It’s not like the destructors of these derived classes perform any critical cleanup task or anything, so what’s the harm if they aren’t called? The fact of the matter is that the C++ standard specifically states that applying delete on a base class pointer to an object of a derived class results in undefined behavior, unless that base class has a virtual destructor. So while calling the wrong destructor may appear to be harmless, even during program execution, in principle anything might happen. If you’re lucky, it’s benign, and nothing bad happens. But it might just as well introduce memory leaks (perhaps only the memory for the base class subobject is freed) or even crash your program.

Virtual Destructors

To ensure that the correct destructor is always called for objects of derived classes that are allocated in the free store, you need virtual class destructors. To implement a virtual destructor in a derived class, you just add the keyword virtual to the destructor declaration in the base class. This signals to the compiler that destructor calls through a pointer or a reference parameter should have dynamic binding, so the destructor that is called will be selected at runtime. This makes the destructor in every class derived from the base class virtual, in spite of the derived class destructors having different names; destructors are treated as a special case for this purpose.

You can see this effect by adding the virtual keyword to the destructor declaration in the Box class of Ex14_07:

class Box
{
protected:
  double length {1.0};
  double width {1.0};
  double height {1.0};
public:
  Box() = default;
  Box(double lv, double wv, double hv) : length {lv}, width {wv}, height {hv} {}
  virtual ∼Box() { std::cout << "Box destructor called" << std::endl; }
  // Remainder of the Box class as before...
};

The destructors of all the derived classes will automatically be virtual as a result of declaring a virtual base class destructor. If you run the example again, the output will confirm that this is so.

If it weren’t for the output message we added for illustration purposes, the function body would have been an empty {} block. Instead of using such an empty block, though, we recommend you declare the destructor using the default keyword. This makes it much more visible that a default implementation is used. For our Box class, you would then write the following:

virtual ∼Box() = default;

The default keyword can be used for all members the compiler would normally generate for you. This includes destructors but also, as you saw earlier, constructors and assignment operators. Note that compiler-generated destructors are never virtual, unless you explicitly declare them as such.

Tip

When polymorphic use is expected (or even just possible), your class must have a virtual destructor to ensure that your objects are always properly destroyed. This implies that as soon as a class has at least one virtual member function, its destructor should be made virtual as well. (The only time you do not have to follow these guidelines is if the nonvirtual destructor is either protected or private, but these are rather exceptional cases.)

Converting Between Pointers to Class Objects

You can implicitly convert a pointer to a derived class to a pointer to a base class, and you can do this for both direct and indirect base classes. For example, let’s first define a smart pointer to a Carton object:

auto pCarton{ std::make_unique<Carton>(30, 40, 10) };

You can convert the pointer that is embedded in this smart pointer implicitly to a pointer to Box, which is a direct base class of Carton:

Box* pBox {pCarton.get()};

The result is a pointer to Box, which is initialized to point to the new Carton object. You know from examples Ex14_05 and Ex14_06 that this also works with references and smart pointers, respectively. A reference to Box, for instance, could be obtained from pCarton as follows:

Box& box {*pCarton};

Note

As a rule, everything we discuss in this section about pointers applies to references as well. We will not always explicitly repeat this, though, nor will we always give analogous examples for references.

Let’s look at converting a pointer to a derived class type to a pointer to an indirect base. Suppose you define a CerealPack class with Carton as the public base class. Box is a direct base of Carton, so it is an indirect base of CerealPack. Therefore, you can write the following:

CerealPack* pCerealPack{ new CerealPack{ 30, 40, 10, "carton" } };
Box* pBox {pCerealPack};

This statement converts the address in pCerealPack from type pointer to CerealPack to type pointer to Box. If you need to specify the conversion explicitly, you can use the static_cast<>() operator:

Box* pBox {static_cast<Box*>(pCerealPack)};

The compiler can usually expedite this cast because Box is a base class of CerealPack. This would not be legal if the Box class was inaccessible or was a virtual base class.

The result of casting a derived class pointer to a base pointer type is a pointer to the subobject of the destination type. It’s easy to get confused when thinking about casting pointers to class types. Don’t forget that a pointer to a class type can only point to objects of that type or to objects of a derived class type and not the other way round. To be specific, the pointer pCarton could contain the address of an object of type Carton (which could be a subobject of a CerealPack object) or an object of type CerealPack. It cannot contain the address of an object of type Box because a CerealPack object is a specialized kind of Carton, but a Box object isn’t. Figure 14-4 illustrates the possibilities between pointers to Box, Carton, and CerealPack objects.

Despite what we have said so far about casting pointers up a class hierarchy, it’s sometimes possible to make casts in the opposite direction. Casting a pointer down a hierarchy from a base to a derived class is different; whether or not a cast works depends on the type of object to which the base pointer is pointing. For a static cast from a base class pointer such as pBox to a derived class pointer such as pCarton to be legal, the base class pointer must be pointing to a Box subobject of a Carton object. If that’s not the case, the result of the cast is undefined. In other words, bad things will happen.
../images/326945_5_En_14_Chapter/326945_5_En_14_Fig4_HTML.gif
Figure 14-4.

Casting pointers up a class hierarchy

Figure 14-5 shows static casts from a pointer, pBox, that contains the address of a Carton object. The cast to type Carton* will work because the object is of type Carton. The result of the cast to type CerealPack*, on the other hand, is undefined because no object of this type exists.
../images/326945_5_En_14_Chapter/326945_5_En_14_Fig5_HTML.gif
Figure 14-5.

Casting pointers down a class hierarchy

If you’re in any doubt about the legitimacy of a static cast, you shouldn’t use it. The success of an attempt to cast a pointer down a class hierarchy depends on the pointer containing the address of an object of the destination type. A static cast doesn’t check whether this is the case, so if you attempt it in circumstances where you don’t know what the pointer points to, you risk an undefined result. Therefore, when you want to cast down a hierarchy, you need to do it differently—in a way in which the cast can be checked at runtime.

Dynamic Casts

A dynamic cast is a conversion that’s performed at runtime. The dynamic_cast<>() operator performs a dynamic cast. You can only apply this operator to pointers and references to polymorphic class types, which are class types that contain at least one virtual function. The reason is that only pointers to polymorphic class types contain the information that the dynamic_cast<>() operator needs to check the validity of the conversion. This operator is specifically for the purpose of converting between pointers or references to class types in a hierarchy. Of course, the types you are casting between must be pointers or references to classes within the same class hierarchy. You can’t use dynamic_cast<>() for anything else. We’ll first discuss casting pointers dynamically.

Casting Pointers Dynamically

There are two kinds of dynamic cast. The first is a “cast down a hierarchy,” from a pointer to a direct or indirect base type to a pointer to a derived type. This is called a downcast . The second possibility is a cast across a hierarchy; this is referred to as a crosscast . Figure 14-6 illustrates these.
../images/326945_5_En_14_Chapter/326945_5_En_14_Fig6_HTML.gif
Figure 14-6.

Downcasts and crosscasts

For a pointer, pBox, of type Box* that contains the address of a CerealPack object, you could write the downcast shown in Figure 14-6 as follows:

Carton* pCarton {dynamic_cast<Carton*>(pBox)};

The dynamic_cast<>() operator is written in the same way as the static_cast<>() operator. The destination type goes between the angled brackets following dynamic_cast, and the expression to be converted to the new type goes between the parentheses. For this cast to be legal, the Box and Carton classes must contain virtual functions, either as declared or inherited members. For the previous cast to work, pBox must point to either a Carton object or a CerealPack object because only objects of these types contain a Carton subobject. If the cast doesn’t succeed, the pointer pCarton will be set to nullptr.

The crosscast in Figure 14-6 could be written as follows:

Contents* pContents {dynamic_cast<Contents*>(pBox)};

As in the previous case, both the Contents class and the Box class must be polymorphic for the cast to be legal. The cast can succeed only if pBox contains the address of a CerealPack object because this is the only type that contains a Contents object and can be referred to using a pointer of type Box*. Again, if the cast doesn’t succeed, nullptr will be stored in pContents.

Using dynamic_cast<>() to cast down a class hierarchy may fail, but in contrast to the static cast, the result will be nullptr rather than just “undefined.” This provides a clue as to how you can use this. Suppose you have some kind of object pointed to by a pointer to Box and you want to call a nonvirtual member function of the Carton class. A base class pointer only allows you to call the virtual member functions of a derived class, but the dynamic_cast<>() operator can enable you to call a nonvirtual function. If surface() is a nonvirtual member function of the Carton class, you could call it with this statement:

dynamic_cast<Carton*>(pBox)->surface();

This is obviously hazardous and in fact little or no better than using static_cast<>(). You need to be sure that pBox is pointing to a Carton object or to an object of a class that has the Carton class as a base. If this is not the case, the dynamic_cast<>() operator returns nullptr, and the outcome of the call again becomes undefined. To fix this, you can use the dynamic_cast<>() operator to determine whether what you intend to do is valid. Here’s an example:

Carton* pCarton {dynamic_cast<Carton*>(pBox)}
if (pCarton)
  pCarton->surface();

Now you’ll only call the surface() function member if the result of the cast is not nullptr. Note that you can’t remove const-ness with dynamic_cast<>(). If the pointer type you’re casting from is const, then the pointer type you are casting to must also be const. If you want to cast from a const pointer to a non-const pointer, you must first cast to a non-const pointer of the same type as the original using the const_cast<>() operator. Recall that using const_cast<>() is rarely recommended, though. Most of the time there is a good reason you have only a const pointer or reference at your disposal, meaning that side-stepping const-ness using const_cast<> often leads to unexpected or inconsistent states.

Caution

A common mistake is to resort to dynamic casts too often, especially in those cases where polymorphism would be more appropriate. If at any time you find code of the following form, know then that you may have to rethink the class design:

Base* base = ...;                                // Start from a pointer-to-Base
auto derived1 = dynamic_cast<Derived1*>(base);   // Try to dynamic_cast Base* to
auto derived2 = dynamic_cast<Derived2*>(base);   // any number of derived types...
...
auto derivedN = dynamic_cast<DerivedN*>(base);
if (derived1)                                    // A chain of if-else statements...
  derived1->DoThis();
else if (derived2)
  derived2->do_this();
...
else if (derivedN)
  derivedN->doThat();

More often than not, such code should be replaced with a solution based on polymorphism. In our fictional example, you should probably create a function doThisOrThat() in the Base class and override it in any derived class that warrants a different implementation. This entire block of code then collapses to this:

Base* base = ...;
base->doThisOrThat();

Not only is this much shorter, this will even keep working if at some point yet another DerivedX is derived from Base. This is precisely the power of polymorphism. Your code does not need to know about all possible derived classes, now or in the future. All it needs to know about is the interface of the base class. Any attempt to mimic this mechanism using dynamic casts is bound to be inferior.

While our previous example was clearly fictional, unfortunately we do see such patterns emerge all too often in real code. So, we do advise you to be very cautious about this!

A related symptom of dynamic_cast misuse we sometimes encounter, albeit less frequently, is a dynamic cast of a this pointer. Such ill-advised code might, for instance, look something like this:

void Base::DoSomething()
{
  if (dynamic_cast<Derived*>(this))          // NEVER DO THIS!
  {
     /* do something else instead... */
     return;
  }
  ...
}

The proper solution here is to make the DoSomething() function virtual and override it in Derived. Downcasting a this pointer is never a good idea, so please don’t ever do this! The code of a base class has no business referring to derived classes. Any variation of this pattern should be replaced with an application of polymorphism. Tip: In general, leveraging polymorphism may involve you splitting a function into multiple functions, some of which you can then override. If you’re interested, this is again related to the so-called template method design pattern. You can find more information online or in other books about this and other standard patterns.

Converting References

You can apply the dynamic_cast<>() operator to a reference parameter in a function to cast down a class hierarchy to produce another reference. In the following example, the parameter to the function doThat() is a reference to a base class Box object. In the body of the function, you can cast the parameter to a reference to a derived type:

double doThat(Box& rBox)
{
  ...
  Carton& rCarton {dynamic_cast<Carton&>(rBox)};
  ...
}

This statement casts from type reference to Box to type reference to Carton. Of course, it’s possible that the object passed as an argument may not be a Carton object, and if this is the case, the cast won’t succeed. There is no such thing as a null reference, so this fails in a different way from a failed pointer cast. Execution of the function stops, and an exception of type std:bad_cast is thrown (this exception class is defined in the typeinfo header of the Standard Library). You haven’t met exceptions yet, but you’ll find out what this means in the next chapter.

So, applying a dynamic cast to a reference blind is obviously risky, but there’s an easy alternative. Simply turn the reference into a pointer and apply the cast to the pointer instead. Then you can again check the resulting pointer for nullptr:

double doThat(Box& rBox)
{
  ...
  Carton* pCarton {dynamic_cast<Carton*>(&rBox)};
  if (pCarton)
  {
     ...
  }
  ...
}

Calling the Base Class Version of a Virtual Function

You’ve seen that it’s easy to call the derived class version of a virtual function through a pointer or reference to a derived class object—the call is made dynamically. However, what do you do when you actually want to call the base class function for a derived class object?

If you override a virtual base class function in a derived class, you’ll often find that the latter is a slight variation of the former. An excellent example of this is the volume() function of the ToughPack class you’ve been using throughout this chapter:

// Function to calculate volume of a ToughPack allowing 15% for packing
double volume() const override { return 0.85 * length * width * height; }

Obviously, the length * width * height part of this return statement is exactly the formula used to compute the volume() in the base class, Box. In this case, the amount of code you had to retype was limited, but this won’t always be the case. It would therefore be much better if you could simply call the base class version of this function instead.

A plausible first attempt to do so in our example case might be this:

double volume() const override { return 0.85 * volume(); }    // infinite recursion!

If you write this, however, the volume() override is simply calling itself, which would then be calling itself again, which would then be calling itself again…. You get the idea; this would result in what we called infinite recursion in Chapter 8 and therefore a program crash. The solution is to explicitly instruct the compiler to call the base class version of the function (you can find a ToughPack.h with this modification in Ex14_07A):

double volume() const override { return 0.85 * Box::volume(); }

Calling the base class version from within a function override like this is common. In some rare cases, though, you may also want to do something similar elsewhere. The Box class provides an opportunity to see why such a call might be required. It could be useful to calculate the loss of volume in a Carton or ToughPack object; one way to do this would be to calculate the difference between the volumes returned from the base and derived class versions of the volume() function. You can force the virtual function for a base class to be called statically by qualifying it with the class name. Suppose you have a pointer pBox that’s defined like this:

Carton carton {40.0, 30.0, 20.0};
Box* pBox {&carton};

You can calculate the loss in total volume for a Carton object with this statement:

double difference {pBox->Box::volume() - pBox->volume()};

The expression pBox->Box::volume() calls the base class version of the volume() function. The class name, together with the scope resolution operator, identifies a particular volume() function, so this will be a static call resolved at compile time.

You can’t use a class name qualifier to force the selection of a particular derived class function in a call through a pointer to the base class. The expression pBox->Carton::volume() won’t compile because Carton::volume() is not a member of the Box class. A call of a function through a pointer is either a static call to a member function of the class type for the pointer or a dynamic call to a virtual function.

Calling the base class version of a virtual function through an object of a derived class can be done analogously. You can calculate the loss in volume for the carton object with this statement:

double difference {carton.Box::volume() - carton.volume()};

Calling Virtual Functions from Constructors or Destructors

Ex14_08 illustrates what happens when you call virtual functions from inside constructors and destructors. As always, we start from a Box class with the necessary debugging statements in its relevant members:

// Box.h
#ifndef BOX_H
#define BOX_H
#include <iostream>
class Box
{
private:
  double length {1.0};
  double width {1.0};
  double height {1.0};
public:
  Box(double lv, double wv, double hv)
    : length {lv}, width {wv}, height {hv}
  {
    std::cout << "Box constructor called for a Box of volume " << volume() << std::endl;
  }
  virtual ∼Box()
  {
    std::cout << "Box destructor called for a Box of volume " << volume() << std::endl;
  }
  // Function to calculate volume of a Box
  virtual double volume() const { return length * width * height; }
  void showVolume() const
  {
    std::cout << "The volume from inside Box::showVolume() is "
              << volume() << std::endl;
  }
};
#endif

We also need a derived class, one that overrides Box::volume():

// ToughPack.h
#ifndef TOUGH_PACK_H
#define TOUGH_PACK_H
#include "Box.h"
class ToughPack : public Box
{
public:
  ToughPack(double lv, double wv, double hv)
    : Box{lv, wv, hv}
  {
    std::cout << "ToughPack constructor called for a Box of volume "
              << volume() << std::endl;
  }
  virtual ∼ToughPack()
  {
    std::cout << "ToughPack destructor called for a Box of volume "
              << volume() << std::endl;
  }
  // Function to calculate volume of a ToughPack allowing 15% for packing
  double volume() const override { return 0.85 * Box::volume(); }
};
#endif

The actual program is trivial. All it does is create an instance of the derived class ToughPack and then show its volume:

// Ex14_08.cpp
// Calling virtual functions from constructors and destructors
#include "Box.h"
#include "ToughPack.h"
int main()
{
  ToughPack toughPack{1.0, 2.0, 3.0};
  toughPack.showVolume();     // Should show a volume equal to 85% of 1x2x3, or 5.1
}

This is the resulting output:

Box constructor called for a Box of volume 6
ToughPack constructor called for a Box of volume 5.1
The volume from inside Box::showVolume() is 5.1
ToughPack destructor called for a Box of volume 5.1
Box destructor called for a Box of volume 6

Let’s first focus our attention on the middle line of this output, which is the product of the toughPack.showVolume() function call. ToughPack overrides volume(), so if you call volume() on a ToughPack object, you expect the version of ToughPack to be used, even if this call originates from inside a base class function such as Box::showVolume(). The output clearly shows that this is also what happens. Box::showVolume() prints out a volume of 0.85 * 1 * 2 * 3, or 5.1, as expected.

Now let’s see what happens if you call volume() not from a regular base class member function such as showVolume() but from a base class constructor. The first line in the output shows you that volume() then returns 6. So, it’s clearly the original function of Box that gets called, not the overridden version of ToughPack! Why is that? You’ll recall from the previous chapter that when an object is constructed, all its subobjects are constructed first, including the subobjects of all its base classes. While initializing such a subobject—for instance, the Box subobject of our ToughPack—the object of the derived class will therefore at most be partially initialized. It would in general be extremely dangerous to call member functions on an object whose subobjects have not yet been fully initialized. This is why all function calls from inside a constructor, including those of virtual members, are always resolved statically.

Conversely, when destructing an object, all its subobjects are destructed in the reverse order in which they were constructed. So, by the time the destructor of the base class subobject is called, the derived class is already partially destructed. It would thus again be a bad idea to call members of this derived object. So, all function calls in destructors are thus resolved statically as well.

Caution

Virtual function calls made from inside a constructor or a destructor are always resolved statically. If you in rare cases do need polymorphic calls during initialization, you should do so from within an init() member function—often virtual itself—that you then call after the construction of the object has completed. This is called the dynamic binding during initialization idiom.

The Cost of Polymorphism

As you know, there’s no such thing as a free lunch, and this certainly applies to polymorphism. You pay for polymorphism in two ways: it requires more memory, and virtual function calls result in additional overhead. These consequences arise because of the way that virtual function calls are typically implemented in practice.

For instance, suppose two classes, A and B, contain identical member variables, but A contains virtual functions, whereas B’s functions are all nonvirtual. In this case, an object of type A requires more memory than an object of type B.

Note

You can create a simple program with two such class objects and use the sizeof operator to see the difference in memory occupied by objects with and without virtual functions.

The reason for the increase in memory is that when you create an object of a polymorphic class type, a special pointer is created in the object. This pointer is used to call any of the virtual functions in the object. The special pointer points to a table of function pointers that gets created for the class. This table, usually called a vtable, has one entry for each virtual function in the class. Figure 14-7 illustrates this.
../images/326945_5_En_14_Chapter/326945_5_En_14_Fig7_HTML.gif
Figure 14-7.

How polymorphic function calls work

When a function is called through a pointer to a base class object, the following sequence of events occurs:
  1. 1.

    The pointer to the vtable in the object pointed to is used to find the beginning of the vtable for the class.

     
  2. 2.

    The entry for the function to be called is found in the vtable, usually by using an offset.

     
  3. 3.

    The function is called indirectly through the function pointer in the vtable. This indirect call is a little slower than a direct call of a nonvirtual function, so each virtual function call carries some overhead.

     

However, the overhead in calling a virtual function is small and shouldn’t give you cause for concern. A few extra bytes per object and slightly slower function calls are small prices to pay for the power and flexibility that polymorphism offers. This explanation is just so you’ll know why the size of an object that has virtual functions is larger than that of an equivalent object that doesn’t.

Note

The only time you should even debate whether the overhead of a virtual function table pointer is worth it is when you have to manage millions and millions of objects of the corresponding type. Suppose you have a Point3D class that represents a point in 3D space. If your program manipulates millions and millions of such points—a Microsoft Kinect, for instance, produces up to 9 million points per second—then avoiding virtual functions in Point3D can save you a significant amount of memory.

Determining the Dynamic Type

Suppose you have a reference to an object of a polymorphic class—which as you recall is a class with at least one virtual function. Then you can determine the dynamic type of this object using the typeid() operator. This standard operator returns a reference to a std:: type_info object that encapsulates the actual type of its operand. Similar in use to the sizeof operator, the operand to the typeid() operator can be either an expression or a type. Concretely, the semantics of the typeid() operator is more or less as follows:
  • If its operand is a type, typeid() evaluates to a reference to a type_info object representing this type.

  • If its operand is any expression that evaluates to a reference to a polymorphic type, this expression is evaluated, and the operand returns the dynamic type of the value referred to by the outcome of this evaluation.

  • If its operand is any other expression, the expression is not evaluated, and the result is the static type of the expression.

The reason we introduce this already more advanced operator to you is because typeid() can be a useful learning or debugging aid. It enables you to easily inspect the type of various expressions or to observe the difference between an object’s static and dynamic type.

Let’s create a program to see this operator in action. We’ll again use the Box and Carton classes of Ex14_06. Of course, by now you know that any base class should have a virtual destructor, so naturally we have given the Box class a defaulted virtual destructor this time. But this small change is not that relevant in this example. Here, we’ll use the Box classes to illustrate typeid()’s behavior with polymorphic classes.

Note

To use the typeid() operator, you always first have to include the typeinfo header from the Standard Library. This makes the std::type_info class available, which is the type of the object returned by the operator. Note that there is an underscore in the name of the type but not in that of the header. Go figure!

// Ex14_09.cpp
// Using the typeid() operator
#include <iostream>
#include <typeinfo>         // For the std::type_info class
#include "Box.h"
#include "Carton.h"
// Define trivial non-polymorphic base and derived classes:
class NonPolyBase {};
class NonPolyDerived : public NonPolyBase {};
Box& GetSomeBox();              // Function returning a reference to a polymorphic type
NonPolyBase& GetSomeNonPoly();  // Function returning a reference to a non-polymorphic type
int main()
{
  // Part 1: typeid() on types and == operator
  std::cout << "Type double has name " << typeid(double).name() << std::endl;
  std::cout << "1 is " << (typeid(1) == typeid(int)? "an int" : "no int") << std::endl;
  // Part 2: typeid() on polymorphic references
  Carton carton{ 1, 2, 3, "paperboard" };
  Box& boxReference = carton;
  std::cout << "Type of carton is "       << typeid(carton).name()       << std::endl;
  std::cout << "Type of boxReference is " << typeid(boxReference).name() << std::endl;
  std::cout << "These are " << (typeid(carton) == typeid(boxReference)? "" : "not ")
            << "equal" << std::endl;
  // Part 3: typeid() on polymorphic pointers
  Box* boxPointer = &carton;
  std::cout << "Type of &carton is "     << typeid(&carton).name()     << std::endl;
  std::cout << "Type of boxPointer is "  << typeid(boxPointer).name()  << std::endl;
  std::cout << "Type of *boxPointer is " << typeid(*boxPointer).name() << std::endl;
  // Part 4: typeid() with non-polymorphic classes
  NonPolyDerived derived;
  NonPolyBase& baseRef = derived;
  std::cout << "Type of baseRef is " << typeid(baseRef).name() << std::endl;
  // Part 5: typeid() on expressions
  const auto& type_info1 = typeid(GetSomeBox());       // function call evaluated
  const auto& type_info2 = typeid(GetSomeNonPoly());   // function call not evaluated
  std::cout << "Type of GetSomeBox() is " << type_info1.name() << std::endl;
  std::cout << "Type of GetSomeNonPoly() is "    << type_info2.name() << std::endl;
}
Box& GetSomeBox()
{
  std::cout << "GetSomeBox() called..." << std::endl;
  static Carton carton{ 2, 3, 5, "duplex" };
  return carton;
}
NonPolyBase& GetSomeNonPoly()
{
  std::cout << "GetSomeNonPoly() called..." << std::endl;
  static NonPolyDerived derived;
  return derived;
}

A possible output of this program looks as follows:

Type double has name double
1 is an int
Type of carton is class Carton
Type of boxReference is class Carton
These are equal
Type of &carton is class Carton *
Type of boxPointer is class Box *
Type of *boxPointer is class Carton
Type of baseRef is class NonPolyBase
GetSomeBox() called...
Type of GetSomeBox() is class Carton
Type of GetSomeNonPoly() is class NonPolyBase

Don’t panic if for you the results look different. The names returned by the name() member function of type_info are not always quite so human readable. With some compilers, the type names that are returned are so-called mangled names, which are the names that the compiler uses internally. If that’s the case, your results might look more like this:

Type double has name d
1 is an int
Type of carton is 6Carton
Type of boxReference is 6Carton
These are equal
Type of &carton is P6Carton
Type of boxPointer is P3Box
Type of *boxPointer is 6Carton
Type of baseRef is 11NonPolyBase
GetSomeBox() called...
Type of GetSomeBox() is 6Carton
Type of GetSomeNonPoly() is 11NonPolyBase

You can consult your compiler’s documentation on how to interpret these names or possibly even on how to convert them to a human-readable format. Normally the mangled names themselves should already carry sufficient information for you to follow this discussion.

The Ex14_09 test program consists of five parts, each illustrating a particular aspect of using the typeid() operator. We’ll discuss each of them in turn.

In the first part, we apply typeid() on a hard-coded type name. In itself, this is not that interesting, at least not until you compare the resulting type_info to the result of applying typeid() to an actual value or expression, as shown in the second statement of main(). Note that the compiler does not perform any implicit conversions with type names. That is, typeid(1) == int is not legal C++; you have to explicitly apply the typeid() operator, as in typeid(1) == typeid(int).

The second part of the program demonstrates that typeid() can indeed be used to determine the dynamic type of an object of a polymorphic type—the main topic of this section. Even though the static type of the boxReference variable is Box&, the program’s output should reflect that typeid() correctly determines the object’s dynamic type: Carton.

The third part of the program shows you that typeid() does not work with pointers in quite the same way as it does with references. Even though boxPointer points to a Carton object, the result of typeid(boxPointer) does not represent Carton*; instead, it simply reflects the static type of boxPointer: Box*. To determine the dynamic type of the object pointed to by a pointer, you therefore have to dereference the pointer first. The outcome of typeid(*boxPointer) shows that this indeed works.

The program’s fourth part illustrates that there is no way to determine the dynamic type of objects of nonpolymorphic types. To test this, we quickly defined two simple classes, NonPolyBase and NonPolyDerived , both trivially nonpolymorphic. Even though baseRef is a reference to an object of dynamic type NonPolyDerived, typeid(baseRef) evaluates to the static type of the expression instead, which is NonPolyBase. You can see the difference if you turn NonPolyBase into a polymorphic class, such as by adding a defaulted virtual destructor like this:

class NonPolyBase { public: virtual ∼NonPolyBase() = default; };

If you then run the program again, the output should show that typeid(baseRef) now resolves to the type_info value for the NonPolyDerived type.

Note

To determine the dynamic type of an object, the typeid() operator needs so-called runtime type information (RTTI for short), which is normally accessed through the object’s vtable.1 Because only objects of polymorphic types contain a vtable reference, typeid() can determine the dynamic type only for objects of polymorphic types. (This, by the way, is also why dynamic_cast<> works only for polymorphic types.)

In the fifth and final part, you learn that the expression passed as an operand to typeid() is evaluated if and only if it has a polymorphic type; from the program’s output, you should be able to read that the GetSomeBox() got called, but GetSomeNonPoly() did not. In a way, this is logical. In the former case, typeid() needs to determine the dynamic type because GetSomeBox() evaluates to a reference to a polymorphic type. Without executing the function, the compiler has no way of determining the dynamic type of its result. The GetSomeNonPoly() function, on the other hand, evaluates to a reference to a nonpolymorphic type. In this case, all the typeid() operator needs is the static type, which is something the compiler already knows at compile time simply by looking at the function’s return type.

Caution

Because this behavior of typeid() can be somewhat unpredictable—sometimes its operand is evaluated, sometimes it is not2—we advise you to never include function calls in the operand to typeid(). If you only apply this operator to either variable names or types, you will avoid any nasty surprises.

Pure Virtual Functions

There are situations that require a base class with a number of classes derived from it and a virtual function that’s redefined in each of the derived classes, but where there’s no meaningful definition for the function in the base class. For example, you might define a base class, Shape, from which you derive classes defining specific shapes, such as Circle, Ellipse, Rectangle, Hexagon, and so on. The Shape class could include a virtual function area() that you’d call for a derived class object to compute the area of a particular shape. The Shape class itself, though, cannot possibly provide a meaningful implementation of the area() function, one that caters, for instance, for both Circles and Rectangles. This is a job for a pure virtual function.

The purpose of a pure virtual function is to enable the derived class versions of the function to be called polymorphically. To declare a pure virtual function rather than an “ordinary” virtual function that has a definition, you use the same syntax but add = 0 to its declaration within the class.

If all this sounds confusing in abstract terms, you can see how to declare a pure virtual function by looking at the concrete example of defining the Shape class we just alluded to:

// Generic base class for shapes
class Shape
{
protected:
  Point position;                  // Position of a shape
  Shape(const Point& shapePosition) : position {shapePosition} {}
public:
  virtual ∼Shape() = default;    // Remember: always use virtual destructors for base classes!
  virtual double area() const = 0;       // Pure virtual function to compute a shape's area
  virtual void scale(double factor) = 0; // Pure virtual function to scale a shape
  // Regular virtual function to move a shape
  virtual void move(const Point& newPosition) { position = newPosition; };  
};

The Shape class contains a member variable of type Point (which is another class type) that stores the position of a shape. It’s a base class member because every shape must have a position, and the Shape constructor initializes it. The area() and scale() functions are virtual because they’re qualified with the virtual keyword, and they are pure because the = 0 following the parameter list specifies that there’s no definition for these functions in this class.

A class that contains at least one pure virtual function is called an abstract class. The Shape class contains two pure virtual functions—area() and scale()—so it is most definitely an abstract class. Let’s look a little more at exactly what this means.

Abstract Classes

Even though it has a member variable, a constructor, and even a member function with an implementation, the Shape class is an incomplete description of an object because the area() and scale() functions are not defined. Therefore, you’re not allowed to create instances of the Shape class; the class exists purely for the purpose of deriving classes from it. Because you can’t create objects of an abstract class, you cannot pass it by value to a function; a parameter of type Shape will not compile. Similarly, you cannot return a Shape by value from a function. However, pointers or references to an abstract class can be used as parameter or return types, so types such as Shape* and Shape& are fine in these settings. It is essential that this should be the case to get polymorphic behavior for derived class objects.

This raises the question, “If you can’t create an instance of an abstract class, then why does the abstract class contain a constructor?” The answer is that the constructor for an abstract class is there to initialize its member variables. The constructor for an abstract class will be called by a derived class constructor, implicitly or from the constructor initialization list. If you try to call the constructor for an abstract class from anywhere else, you’ll get an error message from the compiler.

Because the constructor for an abstract class can’t be used generally, it’s a good idea to declare it as a protected member of the class, as we have done for the Shape class. This allows it to be called in the initialization list for a derived class constructor but prevents access to it from anywhere else. Note that a constructor for an abstract class must not call a pure virtual function; the effect of doing so is undefined.

Any class that derives from the Shape class must define both the area() function and the scale() function. If it doesn’t, it too is an abstract class. More specifically, if any pure virtual function of an abstract base class isn’t defined in a derived class, then the pure virtual function will be inherited as such, and the derived class will also be an abstract class.

To illustrate this, you could define a new class called Circle, which has the Shape class as a base:

// A macro defining the mathematical constant π
#define PI 3.141592653589793238462643383279502884
// Class defining a circle
class Circle : public Shape
{
protected:
  double radius;        // Radius of a circle
public:
  Circle(const Point& center, double circleRadius) : Shape{center}, radius{circleRadius} {}
  double area() const override { return radius * radius * PI; }
  void scale(double factor) override { radius *= factor; }
};

The area() and scale() functions are defined, so this class is not abstract. If either function were not defined, then the Circle class would be abstract. The class includes a constructor, which initializes the base class subobject by calling the base class constructor.

Of course, an abstract class can contain virtual functions that it does define and functions that are not virtual. An example of the former was the move() function in Shape. It can also contain any number of pure virtual functions.

Let’s look at a working example that uses an abstract class. We’ll define a new version of the Box class with the volume() function declared as a pure virtual function. As a polymorphic base class, it of course needs a virtual destructor as well:

class Box
{
protected:
  double length {1.0};
  double width {1.0};
  double height {1.0};
  Box(double lv, double wv, double hv) : length {lv}, width {wv}, height {hv} {}
public:
  virtual ∼Box() = default;            // Virtual destructor
  virtual double volume() const = 0;   // Function to calculate the volume
};

Because Box is now an abstract class, you can no longer create objects of this type. This would not have been possible even if we hadn’t made the constructor protected. Because these constructors are only intended for use in derived classes, however, it makes sense to declare them protected. The Carton and ToughPack classes in this example are the same as in Ex14_06. They both define the volume() function, so they aren’t abstract, and we can use objects of these classes to show that the virtual volume() functions are still working as before:

// Ex14_10.cpp
// Using an abstract class
#include <iostream>
#include "Box.h"                                 // For the Box class
#include "ToughPack.h"                           // For the ToughPack class
#include "Carton.h"                              // For the Carton class
int main()
{
  ToughPack hardcase {20.0, 30.0, 40.0};         // A derived box - same size
  Carton carton {20.0, 30.0, 40.0, "plastic"};   // A different derived box
  Box* pBox {&hardcase};                         // Base pointer - derived address
  std::cout << "hardcase volume is " << pBox->volume() << std::endl;
  pBox = &carton;                                // New derived address
  std::cout << "carton volume is " << pBox->volume() << std::endl;
}

This generates the following output:

hardcase volume is 20400
carton volume is 22722.4

Declaring volume() to be a pure virtual function in the Box class ensures that the volume() function members of the Carton and ToughPack classes are also virtual. Therefore, you can call them through a pointer to the base class, and the calls will be resolved dynamically. The output for the ToughPack and Carton objects shows that everything is working as expected. The Carton and ToughPack class constructors still call the Box class constructor that is now protected in their initialization lists.

Note

You can now no longer implement the volume() member of the ToughPack class like we did in Ex14_09:

double volume() const override { return 0.85 * Box::volume(); }

The Box::volume() is now a pure virtual function, and you can never call pure virtual function using static binding (it has no function body!). Because no base implementation is provided anymore, you’ll again have to spell out length * width * height here.

Abstract Classes as Interfaces

Sometimes an abstract class arises simply because a function has no sensible definition in the context of the class and has a meaningful interpretation only in a derived class. However, there is another way of using an abstract class. An abstract class that contains only pure virtual functions—no member variables or other functions—can be used to define what in object-oriented terminology is often called an interface. It would typically represent a declaration of a set of related functions that supported a particular capability—a set of functions for communications through a modem, for example. While other programming languages such as Java and C# have specific class-like language constructs for this, in C++ one defines an interface using an abstract class consisting solely of pure virtual functions. As we’ve discussed, a class that derives from such an abstract base class must define an implementation for each virtual function, but the way in which each virtual function is implemented is specified by whoever is implementing the derived class. The abstract class fixes the interface, but the implementation in the derived class is flexible.

Because the abstract Shape and Box classes from the previous section have member variables, they’re not really interface class. An example of an interface would be the following Vessel class, defined in Vessel.h. All it does is specify that any Vessel has a volume, which may be obtained from its (pure virtual) volume() member function:

// Vessel.h Abstract class defining a vessel
#ifndef VESSEL_H
#define VESSEL_H
class Vessel {
public:
  virtual ∼Vessel() = default;         // As always: a virtual destructor!
  virtual double volume() const = 0;
};
#endif

There could be any number of classes implementing the Vessel interface. All would implement the interface’s volume() function in their own way. Our first Vessel class will be, naturally, our trusty old Box class:

class Box : public Vessel
{
protected:
  double length {1.0};
  double width {1.0};
  double height {1.0};
public:
  Box() = default;
  Box(double lv, double wv, double hv) : length {lv}, width {wv}, height {hv} {}
  double volume() const override { return length * width * height; }
};

This makes any classes derived from Box valid Vessels as well. You can, for instance, use the Carton and ToughPack classes from Ex14_09 (although you can and should again call Box::volume() now from within its ToughPack override since now this base class function is no longer pure virtual).

Of course, you can also add another class derived from Vessel. One example would be a class that defines a can. Here is a class definition you can place in Can.h:

// Can.h Class defining a cylindrical can of a given height and diameter
#ifndef CAN_H
#define CAN_H
#include "Vessel.h"
// A macro defining the mathematical constant π
#define PI 3.141592653589793238462643383279502884
class Can : public Vessel
{
protected:
  double diameter {1.0};
  double height {1.0};
public:
  Can(double d, double h) : diameter {d}, height {h} {}    
  double volume() const override { return PI * diameter * diameter * height / 4.0; }
};
#endif

This defines Can objects that represent regular cylindrical cans, such as a beer can. The class uses the same PI macro we discussed earlier. Note that it might be better to define such constants in a separate header, say math_constants.h, instead. This would then allow you to reuse these constants in multiple header and source files.

You can find the program that glues everything together in Ex14_11.cpp:

// Ex14_11.cpp
// Using an interface class and indirect base classes
#include <iostream>
#include <vector>                      // For the vector container
#include "Box.h"                       // For the Box class
#include "ToughPack.h"                 // For the ToughPack class
#include "Carton.h"                    // For the Carton class
#include "Can.h"                       // for the Can class
int main()
{
  Box box {40, 30, 20};
  Can can {10, 3};
  Carton carton {40, 30, 20, "Plastic"};
  ToughPack hardcase {40, 30, 20};
  std::vector<Vessel*> vessels {&box, &can, &carton, &hardcase};
  for (const auto* vessel : vessels)
    std::cout << "Volume is " << vessel->volume() << std::endl;
}

This generates the following output:

Volume is 24000
Volume is 235.619
Volume is 22722.4
Volume is 20400

This time around, we used a vector of raw pointers to Vessel objects to exercise the virtual functions. The output shows that all the polymorphic calls of the volume() function work as expected.

You have a three-level class hierarchy in this example, as shown in Figure 14-8.
../images/326945_5_En_14_Chapter/326945_5_En_14_Fig8_HTML.gif
Figure 14-8.

A three-level class hierarchy

Recall that if a derived class fails to define a function that’s declared as a pure virtual function in the base class, then the function will be inherited as a pure virtual function, and this will make the derived class an abstract class. You can demonstrate this effect by removing the const declaration from either the Can or the Box class. This makes the function different from the pure virtual function in the base class, so the derived class inherits the base class version, and the program won’t compile.

Summary

In this chapter, we covered the principal ideas involved in using inheritance. These are the fundamentals that you should keep in mind:
  • Polymorphism involves calling a (virtual) member function of a class through a pointer or a reference and having the call resolved dynamically. That is, the particular function to be called is determined by the object that is pointed to or referenced when the program is executing.

  • A function in a base class can be declared as virtual. All occurrences of the function in classes that are derived from the base will then be virtual too.

  • You should always declare the destructor of classes intended to be used as a base class as virtual (often this can be done in combination with = default). This ensures correct selection of a destructor for dynamically created derived class objects. It suffices to do so for the most base class, but it does not hurt to do it elsewhere either.

  • You should use the override qualifier with each member function of a derived class that overrides a virtual base class member. This causes the compiler to verify that the functions signatures in the base and derived classes are, and forever remain, the same.

  • The final qualifier may be used on an individual virtual function override to signal that it may not be overridden any further. If an entire class is specified to be final, no derived classes can be defined for it anymore.

  • Default argument values for parameters in virtual functions are assigned statically, so if default values for a base version of a virtual function exist, default values specified in a derived class will be ignored for dynamically resolved function calls.

  • The dynamic_cast<> operator is generally used to cast from a pointer-to-a-polymorphic-base-class to a pointer-to-a-derived-class. If the pointer does not point to an object of the given derived class type, dynamic_cast<> evaluates to nullptr. This type check is performed dynamically, at runtime.

  • A pure virtual function has no definition. A virtual function in a base class can be specified as pure by placing = 0 at the end of the member function declaration.

  • A class with one or more pure virtual functions is called an abstract class, for which no objects can be created. In any class derived from an abstract class, all the inherited pure virtual functions must be defined. If they’re not, it too becomes an abstract class, and no objects of the class can be created.

Exercises

The following exercises enable you to try what you’ve learned in this chapter. If you get stuck, look back over the chapter for help. If you’re still stuck after that, you can download the solutions from the Apress website ( www.apress.com/source-code ), but that really should be a last resort.
  • Exercise 14-1. Define a base class called Animal with two member variables: a string member to store the name of the animal (e.g., "Fido") and an integer member, weight, that will contain the weight of the Animal in pounds. Also include a public member function, who(), that returns a string object containing the name and weight of the Animal object, as well as a pure virtual function called sound() that in a derived class should return a string representing the sound the animal makes. Derive at least three classes—Sheep, Dog, and Cow—with the class Animal as a public base, and implement the sound() function appropriately in each class.

  • Define a class called Zoo that can store the addresses of any number of Animal objects of various types in a vector<> container. Write a main() function to create a random sequence of an arbitrary number of objects of classes derived from Animal and store pointers to them in a Zoo object. To keep things simple, work with std::shared_ptr<> pointers to transfer and store Animals into the Zoo. (Later, in Chapter 17, we’ll teach you about move semantics, which will allow you to use unique_ptr<> smart pointers for this as well.) The number of objects should be entered from the keyboard. Define a member function of the Zoo class that outputs information about each animal in the Zoo, including the text of the sound they all make.

  • Exercise 14-2. Start from the solution of Exercise 14-1. Because Cows are notoriously self-conscious about their weight, the result of the who() function of this class must no longer include the weight of the animal. Sheep, on the other hand, are whimsical creatures. They tend to prefix their name with "Woolly"—that is, for a Sheep called "Pete" who() should return a string containing "Woolly Pete". Besides that, it should also reflect a Sheep’s true weight, which is its total weight (as stored in the Animal base object) minus that of its wool (known by the Sheep itself). Say that a new Sheep’s wool by default weighs 10 percent of his total weight.

  • Exercise 14-3. Can you think of a way to implement the requirements of Exercise 14-2 without overriding who() in the Sheep class? (Hint: Perhaps Animal::who() could call polymorphic functions to obtain the name and weight of an Animal.)

  • Exercise 14-4. Add a function herd() to the Zoo class you made for Exercises 14-2 or 14-3 that returns a vector<Sheep*> with pointers to all Sheep in the Zoo. The Sheep remain part of the Zoo. Define a function shear() for Sheep that removes their wool. The function returns the weight of the wool after correctly adjusting the weight members of the Sheep object. Adjust the program of Exercise 14-2 such that it gathers all Sheep using herd(), collects all their wool, and then outputs information in the Zoo again.

  • Hint: To extract an Animal* pointer from a given shared_ptr<Animal>, you call the get() function of the std::shared_ptr<> template.

  • Extra: In this chapter, you learned about two different language mechanisms that could be used to herd() Sheep, that is, two techniques to differentiate Sheep* from other Animal* pointers. Try both (leaving one commented out).

  • Exercise 14-5. You may have wondered why for the herd() function in Exercise 14-4 we asked you to switch from using Animal shared_ptr<>s to raw Sheep* pointers. Shouldn’t that have been shared_ptr<Sheep> instead? The main problem is that you cannot simply cast a shared_ptr<Animal> to a shared_ptr<Sheep>. These are unrelated types as far as the compiler is concerned. But you are correct; it probably would’ve been better to use shared_ptr<Sheep>, and we were probably underestimating your capabilities there. All you really need to know is that to cast between shared_ptr<Animal> and shared_ptr<Sheep> you mustn’t use the built-in dynamic_cast<> and static_cast<> operators, but instead the std::dynamic_pointer_cast<> and std::static_pointer_cast<> Standard Library functions defined in the <memory> header. For instance, let shared_animal be a shared_ptr<Animal>. Then dynamic_pointer_cast<Sheep>(shared_animal) results in a shared_ptr<Sheep>. If shared_animal points to a Sheep, the resulting smart pointer will refer to that Sheep; if not, it will contain nullptr. Adapt the solution of Exercise 14-4 to properly use smart pointers everywhere.

  • Exercise 14-6. Start from the Shape and Circle classes we used earlier to introduce abstract classes. Create one more Shape derivative, Rectangle, that has a width and a height. Introduce an extra function perimeter() that computes a shape’s perimeter. Define a main() program that starts by filling a polymorphic vector<> with a number of Shapes (a hard-coded list of Shapes is fine; there’s no need to generate them randomly). Next, you should print out the total sum of their areas and perimeters, scale all Shapes with a factor 1.5, and then print out these same sums again. Of course, you haven’t forgotten what you learned in the first half of this book, so you shouldn’t put all code inside main() itself. Define the appropriate amount of helper functions!

  • Hint: For a circle with radius r, the perimeter (or circumference) is computed using the formula 2πr.