Lesson 11. Polymorphism

Having learned the basics of inheritance, creating an inheritance hierarchy, and understanding that public inheritance essentially models an is-a relationship, it’s time to move on to consuming this knowledge in learning the holy grail of object-oriented programming: polymorphism.

In this lesson, you find out

Image What polymorphism actually means

Image What virtual functions do and how to use them

Image What abstract base classes are and how to declare them

Image What virtual inheritance means and where you need it

Basics of Polymorphism

“Poly” is Greek for many, and “morph” means form. Polymorphism is that feature of object-oriented languages that allows objects of different types to be treated similarly. This lesson focuses on polymorphic behavior that can be implemented in C++ via the inheritance hierarchy, also known as subtype polymorphism.

Need for Polymorphic Behavior

In Lesson 10, “Implementing Inheritance,” you found out how Tuna and Carp inherit public method Swim() from Fish as shown in Listing 10.1. It is, however, possible that both Tuna and Carp provide their own Tuna::Swim() and Carp::Swim() methods to make Tuna and Carp different swimmers. Yet, as each of them is also a Fish, if a user with an instance of Tuna uses the base class type to invoke Fish::Swim(), he ends up executing only the generic part Fish::Swim() and not Tuna::Swim(), even though that base class instance Fish is a part of a Tuna. This problem is demonstrated in Listing 11.1.


Note

All the code samples in this lesson have been stripped to the bare essentials required to explain the topic in question and to keep the number of lines of code to a minimum to improve readability.

When you are programming, you should program your classes correctly and create inheritance hierarchies that make sense, keeping the design and purpose of the application in perspective.


LISTING 11.1 Invoking Methods Using an Instance of the Base Class Fish That Belongs to a Tuna


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Fish
  4: {
  5: public:
  6:    void Swim()
  7:    {
  8:       cout << "Fish swims! " << endl;
  9:    }
 10: };
 11:
 12: class Tuna:public Fish
 13: {
 14: public:
 15:    // override Fish::Swim
 16:    void Swim()
 17:    {
 18:       cout << "Tuna swims!" << endl;
 19:    }
 20: };
 21:
 22: void MakeFishSwim(Fish& inputFish)
 23: {
 24:    // calling Fish::Swim
 25:    inputFish.Swim();
 26: }
 27:
 28: int main()
 29: {
 30:    Tuna myDinner;
 31:
 32:    // calling Tuna::Swim
 33:    myDinner.Swim();
 34:
 35:    // sending Tuna as Fish
 36:    MakeFishSwim(myDinner);
 37:
 38:    return 0;
 39: }


Output Image

Tuna swims!
Fish swims!

Analysis Image

class Tuna specializes class Fish via public inheritance as shown in Line 12. It also overrides Fish::Swim(). main() makes a direct call to Tuna::Swim() in Line 33 and passes myDinner (of type Tuna) as a parameter to MakeFishSwim() that interprets it as a reference Fish&, as shown in the declaration at Line 22. In other words, MakeFishSwim(Fish&) doesn’t care if the object sent was a Tuna, handles it as a Fish, and invokes Fish::Swim(). So, the second line of output indicates that the same object Tuna produced the output of a Fish not indicating any specialization thereof (this could as well be a Carp).

What the user would ideally expect is that an object of type Tuna behaves like a tuna even if the method invoked is Fish::Swim(). In other words, when inputFish.Swim() is invoked in Line 25, he expects it to execute Tuna::Swim(). Such polymorphic behavior where an object of known type class Fish can behave as its actual type; namely, derived class Tuna, can be implemented by making Fish::Swim() a virtual function.

Polymorphic Behavior Implemented Using Virtual Functions

You have access to an object of type Fish, via pointer Fish* or reference Fish&. This object could have been instantiated solely as a Fish, or be part of a Tuna or Carp that inherits from Fish. You don’t know (and don’t care). You invoke method Swim() using this pointer or reference, like this:

pFish->Swim();
myFish.Swim();

What you expect is that the object Fish swims as a Tuna if it is part of a Tuna, as a Carp if it is part of a Carp, or an anonymous Fish if it wasn’t instantiated as part of a specialized class such as Tuna or Carp. You can ensure this by declaring function Swim() in the base class Fish as a virtual function:

class Base
{
   virtual ReturnType FunctionName (Parameter List);
};
class Derived
{
   ReturnType FunctionName (Parameter List);
};

Use of keyword virtual means that the compiler ensures that any overriding variant of the requested base class method is invoked. Thus, if Swim() is declared virtual, invoking myFish.Swim() (myFish being of type Fish&) results in Tuna::Swim() being executed as demonstrated by Listing 11.2.

LISTING 11.2 The Effect of Declaring Fish::Swim() as a virtual Method


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Fish
  4: {
  5: public:
  6:    virtual void Swim()
  7:    {
  8:       cout << "Fish swims!" << endl;
  9:    }
 10: };
 11:
 12: class Tuna:public Fish
 13: {
 14: public:
 15:    // override Fish::Swim
 16:    void Swim()
 17:    {
 18:       cout << "Tuna swims!" << endl;
 19:    }
 20: };
 21:
 22: class Carp:public Fish
 23: {
 24: public:
 25:    // override Fish::Swim
 26:    void Swim()
 27:    {
 28:       cout << "Carp swims!" << endl;
 29:    }
 30: };
 31:
 32: void MakeFishSwim(Fish& inputFish)
 33: {
 34:    // calling virtual method Swim()
 35:    inputFish.Swim();
 36: }
 37:
 38: int main()
 39: {
 40:    Tuna myDinner;
 41:    Carp myLunch;
 42:
 43:    // sending Tuna as Fish
 44:    MakeFishSwim(myDinner);
 45:
 46:    // sending Carp as Fish
 47:    MakeFishSwim(myLunch);
 48:
 49:    return 0;
 50: }


Output Image

Tuna swims!
Carp swims!

Analysis Image

The implementation of function MakeFishSwim(Fish&) has not changed one bit since Listing 11.1. Yet, the output it produces is dramatically different. For one, Fish::Swim() has not been invoked at all because of the presence of overriding variants Tuna::Swim() and Carp::Swim() that have taken priority over Fish::Swim() because the latter has been declared as a virtual function. This is a very important development. It implies that even without knowing the exact type of Fish being handled, the implementation MakeFishSwim() could result in different implementations of Swim() defined in different derived classes being invoked, given only a base class instance.

This is polymorphism: treating different fishes as a common type Fish, yet ensuring that the right implementation of Swim() supplied by the derived types is executed.

Need for Virtual Destructors

There is a more sinister side to the feature demonstrated by Listing 11.1—unintentionally invoking base class functionality of an instance of type derived, when a specialization is available. What happens when a function calls operator delete using a pointer of type Base* that actually points to an instance of type Derived?

Which destructor would be invoked? See Listing 11.3.

LISTING 11.3 A Function That Invokes Operator delete on Base*


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Fish
  4: {
  5: public:
  6:    Fish()
  7:    {
  8:       cout << "Constructed Fish" << endl;
  9:    }
 10:    ~Fish()
 11:    {
 12:       cout << "Destroyed Fish" << endl;
 13:    }
 14: };
 15:
 16: class Tuna:public Fish
 17: {
 18: public:
 19:    Tuna()
 20:    {
 21:       cout << "Constructed Tuna" << endl;
 22:    }
 23:    ~Tuna()
 24:    {
 25:       cout << "Destroyed Tuna" << endl;
 26:    }
 27: };
 28:
 29: void DeleteFishMemory(Fish* pFish)
 30: {
 31:    delete pFish;
 32: }
 33:
 34: int main()
 35: {
 36:    cout << "Allocating a Tuna on the free store:" << endl;
 37:    Tuna* pTuna = new Tuna;
 38:    cout << "Deleting the Tuna: " << endl;
 39:    DeleteFishMemory(pTuna);
 40:
 41:    cout << "Instantiating a Tuna on the stack:" << endl;
 42:    Tuna myDinner;
 43:    cout << "Automatic destruction as it goes out of scope: " << endl;
 44:
 45:    return 0;
 46: }


Output Image

Allocating a Tuna on the free store:
Constructed Fish
Constructed Tuna
Deleting the Tuna:
Destroyed Fish
Instantiating a Tuna on the stack:
Constructed Fish
Constructed Tuna
Automatic destruction as it goes out of scope:
Destroyed Tuna
Destroyed Fish

Analysis Image

main() creates an instance of Tuna on the free store using new at Line 37 and then releases the allocated memory immediately after using service function DeleteFishMemory() at Line 39. For the sake of comparison, another instance of Tuna is created as a local variable myDinner on the stack at Line 42 and goes out of scope when main() ends. The output is created by the cout statements in the constructors and destructors of classes Fish and Tuna. Note that while Tuna and Fish were both constructed on the free store due to new, the destructor of Tuna was not invoked during delete, rather only that of the Fish. This is in stark contrast to the construction and destruction of local member myDinner where all constructors and destructors are invoked. Lesson 10 demonstrated in Listing 10.7 the correct order of construction and destruction of classes in an inheritance hierarchy, showing that all destructors need to be invoked, including ~Tuna(). Clearly, something is amiss.

This flaw means that the destructor of a deriving class that has been instantiated on the free store using new would not be invoked if delete is called using a pointer of type Base*. This can result in resources not being released, memory leaks, and so on and is a problem that is not to be taken lightly.

To avoid this problem, you use virtual destructors as seen in Listing 11.4.

LISTING 11.4 Using virtual Destructors to Ensure That Destructors in Derived Classes Are Invoked When Deleting a Pointer of Type Base*


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Fish
  4: {
  5: public:
  6:    Fish()
  7:    {
  8:       cout << "Constructed Fish" << endl;
  9:    }
 10:    virtual ~Fish()   // virtual destructor!
 11:    {
 12:       cout << "Destroyed Fish" << endl;
 13:    }
 14: };
 15:
 16: class Tuna:public Fish
 17: {
 18: public:
 19:    Tuna()
 20:    {
 21:       cout << "Constructed Tuna” << endl;
 22:    }
 23:    ~Tuna()
 24:    {
 25:       cout << "Destroyed Tuna" << endl;
 26:    }
 27: };
 28:
 29: void DeleteFishMemory(Fish* pFish)
 30: {
 31:    delete pFish;
 32: }
 33:
 34: int main()
 35: {
 36:    cout << "Allocating a Tuna on the free store:" << endl;
 37:    Tuna* pTuna = new Tuna;
 38:    cout << "Deleting the Tuna: " << endl;
 39:    DeleteFishMemory(pTuna);
 40:
 41:    cout << "Instantiating a Tuna on the stack:" << endl;
 42:    Tuna myDinner;
 43:    cout << "Automatic destruction as it goes out of scope: " << endl;
 44:
 45:    return 0;
 46: }


Output Image

Allocating a Tuna on the free store:
Constructed Fish
Constructed Tuna
Deleting the Tuna:
Destroyed Tuna
Destroyed Fish
Instantiating a Tuna on the stack:
Constructed Fish
Constructed Tuna
Automatic destruction as it goes out of scope:
Destroyed Tuna
Destroyed Fish

Analysis Image

The only improvement in Listing 11.4 over Listing 11.3 is the addition of keyword virtual at Line 10 where the destructor of base class Fish has been declared. Note that this small change resulted in the compiler essentially executing Tuna::~Tuna() in addition to Fish::~Fish() when operator delete is invoked on Fish* that actually points to a Tuna, as shown in Line 31. Now, this output also demonstrates that the sequence and the invocation of constructors and destructors are the same irrespective of whether the object of type Tuna is instantiated on the free store using new, as shown in Line 37, or as a local variable on the stack, as shown in Line 42.


Note

Always declare the base class destructor as virtual:

class Base
{
public:
   virtual ~Base() {}; // virtual destructor
};

This ensures that one with a pointer Base* cannot invoke delete in a way that instances of the deriving classes are not correctly destroyed.


How Do virtual Functions Work? Understanding the Virtual Function Table


Note

This section is optional toward learning to use polymorphism. Feel free to skip it or read it to feed your curiosity.


Function MakeFishSwim(Fish&) in Listing 11.2 ends up invoking Carp::Swim() or Tuna::Swim() methods in spite of the programmer calling Fish::Swim() within it. Clearly, at compile time, the compiler knows nothing about the nature of objects that such a function will encounter to be able to ensure that the same function ends up executing different Swim() methods at different points in time. The Swim() method that needs to be invoked is evidently a decision made at runtime, using a logic that implements polymorphism, which is supplied by the compiler at compile-time.

Consider a class Base that declared N virtual functions:

class Base
{
public:
   virtual void Func1()
   {
      // Func1 implementation
   }
   virtual void Func2()
   {
      // Func2 implementation
   }

   // .. so on and so forth
   virtual void FuncN()
   {
      // FuncN implementation
   }
};

class Derived that inherits from Base overrides Base::Func2(), exposing the other virtual functions directly from class Base:

class Derived: public Base
{
public:
   virtual void Func1()
   {
      // Func2 overrides Base::Func2()
   }

   // no implementation for Func2()

   virtual void FuncN()
   {
      // FuncN implementation
   }
};

The compiler sees an inheritance hierarchy and understands that the Base defines certain virtual functions that have been overridden in Derived. What the compiler now does is to create a table called the Virtual Function Table (VFT) for every class that implements a virtual function or derived class that overrides it. In other words, classes Base and Derived get an instance of their own Virtual Function Table. When an object of these classes is instantiated, a hidden pointer (let’s call it VFT*) is initialized to the respective VFT. The Virtual Function Table can be visualized as a static array containing function pointers, each pointing to the virtual function (or override) of interest, as illustrated in Figure 11.1.

Image

FIGURE 11.1 Visualization of a Virtual Function Table for classes Derived and Base.

Thus, each table is comprised of function pointers, each pointing to the available implementation of a virtual function. In the case of class Derived, all except one function pointer in its VFT point to local implementations of the virtual method in Derived. Derived has not overridden Base::Func2(), and hence that function pointer points to the implementation in class Base.

This means that when a user of class Derived calls

CDerived objDerived;
objDerived.Func2();

the compiler ensures a lookup in the VFT of class Derived and ensures that the implementation Base::Func2() is invoked. This also applies to calls that use methods that have been virtually overridden:

void DoSomething(Base& objBase)
{
   objBase.Func1();   // invoke Derived::Func1
}
int main()
{
   Derived objDerived;
   DoSomething(objDerived);
};

In this case, even though objDerived is being interpreted via objBase as an instance of class Base, the VFT pointer in this instance is still pointing to the same table created for class Derived. Thus, Func1() executed via this VFT is certainly Derived::Func1().

This is how Virtual Function Tables help the implementation of (subtype) polymorphism in C++.

The proof of existence of a hidden Virtual Function Table pointer is demonstrated by Listing 11.5, which compares the sizeof two identical classes—one that has virtual functions and another that doesn’t.

LISTING 11.5 Demonstrating the Presence of a Hidden VFT Pointer in Comparing Two Classes Identical but for a Function Declared Virtual


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class SimpleClass
  4: {
  5:    int a, b;
  6:
  7: public:
  8:    void DoSomething() {}
  9: };
 10:
 11: class Base
 12: {
 13:    int a, b;
 14:
 15: public:
 16:    virtual void DoSomething() {}
 17: };
 18:
 19: int main()
 20: {
 21:    cout << "sizeof(SimpleClass) = " << sizeof(SimpleClass) << endl;
 22:    cout << "sizeof(Base) = " << sizeof(Base) << endl;
 23:
 24:    return 0;
 25: }


Output Using 32-Bit Compiler Image

sizeof(SimpleClass) = 8
sizeof(Base) = 12

Output Using 64-Bit Compiler Image

sizeof(SimpleClass) = 8
sizeof(Base) = 16

Analysis Image

This is a sample that has been stripped to the bare minimum. You see two classes, SimpleClass and Base, that are identical in the types and number of members, yet Base has the function DoSomething() declared as virtual (nonvirtual in SimpleClass). The difference in adding this virtual keyword is that the compiler generates a virtual function table for class Base and a reserved place for a pointer to the same in Base as a hidden member. This pointer consumes the 4 extra bytes in my 32-bit system and is the proof of the pudding.


Note

C++ also allows you to query a pointer Base* if it is of type Derived* using casting operator dynamic_cast and then perform conditional execution on the basis of the result of the query.

This is called runtime type identification (RTTI) and should ideally be avoided even though it is supported by most C++ compilers. This is because needing to know the type of derived class object behind a base class pointer is commonly considered poor programming practice.

RTTI and dynamic_cast are discussed in Lesson 13, “Casting Operators.


Abstract Base Classes and Pure Virtual Functions

A base class that cannot be instantiated is called an abstract base class. Such a base class fulfills only one purpose, that of being derived from. C++ allows you to create an abstract base class using pure virtual functions.

A virtual method is said to be pure virtual when it has a declaration as shown in the following:

class AbstractBase
{
public:
   virtual void DoSomething() = 0;  // pure virtual method
};

This declaration essentially tells the compiler that DoSomething() needs to be implemented and by the class that derives from AbstractBase:

class Derived: public AbstractBase
{
public:
   void DoSomething()   // pure virtual fn. must be implemented
   {
      cout << "Implemented virtual function" << endl;
   }
};

Thus, what class AbstractBase has done is that it has enforced class Derived to supply an implementation for virtual method DoSomething(). This functionality where a base class can enforce support of methods with a specified name and signature in classes that derive from it is that of an interface. Think of a Fish again. Imagine a Tuna that cannot swim fast because Tuna did not override Fish::Swim(). This is a failed implementation and a flaw. Making class Fish an abstract base class with Swim as a pure virtual function ensures that Tuna that derives from Fish implements Tuna::Swim() and swims like a Tuna and not like just any Fish. See Listing 11.6.

LISTING 11.6 class Fish as an Abstract Base Class for Tuna and Carp


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Fish
  4: {
  5: public:
  6:    // define a pure virtual function Swim
  7:    virtual void Swim() = 0;
  8: };
  9:
 10: class Tuna:public Fish
 11: {
 12: public:
 13:    void Swim()
 14:    {
 15:       cout << "Tuna swims fast in the sea! " << endl;
 16:    }
 17: };
 18:
 19: class Carp:public Fish
 20: {
 21:    void Swim()
 22:    {
 23:       cout << "Carp swims slow in the lake!" << endl;
 24:    }
 25: };
 26:
 27: void MakeFishSwim(Fish& inputFish)
 28: {
 29:    inputFish.Swim();
 30: }
 31:
 32: int main()
 33: {
 34:    // Fish myFish; // Fails, cannot instantiate an ABC
 35:    Carp myLunch;
 36:    Tuna myDinner;
 37:
 38:    MakeFishSwim(myLunch);
 39:    MakeFishSwim(myDinner);
 40:
 41:    return 0;
 42: }


Output Image

Carp swims slow in the lake!
Tuna swims fast in the sea!

Analysis Image

The first line in main() at Line 34 (commented out) is significant. It demonstrates that the compiler does not allow you to create an instance of an abstract base class (‘ABC’) Fish. It expects something concrete, such as a specialization of Fish—for example, Tuna—which makes sense even in the real-world arrangement of things. Thanks to the pure virtual function Fish::Swim() declared in Line 7, both Tuna and Carp are forced into implementing Tuna::Swim() and Carp::Swim(). Lines 27–30 that implement MakeFishSwim(Fish&) demonstrate that even if an abstract base class cannot be instantiated, you can use it as a reference or a pointer. Abstract base classes are thus a very good mechanism to declare functions that you expect derived classes to implement and fulfill. If a class Trout that derived from Fish forgets to implement Trout::Swim(), the compilation also fails.


Note

Abstract Base Classes are often simply called ABCs.


ABCs help enforce certain design constraints on your program.

Using virtual Inheritance to Solve the Diamond Problem

In Lesson 10 you saw the curious case of a duck-billed platypus that is part mammal, part bird, and part reptile. This is an example where a class Platypus needs to inherits from class Mammal, class Bird, and class Reptile. However, each of these in turn inherits from a more generic class Animal, as illustrated in Figure 11.2.

Image

FIGURE 11.2 The class diagram of a platypus demonstrating multiple inheritance.

So, what happens when you instantiate a Platypus? How many instances of class Animal are instantiated for one instance of Platypus? Listing 11.7 helps answer this question.

LISTING 11.7 Checking for the Number of Base Class Animal Instances for One Instance of Platypus


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Animal
  4: {
  5: public:
  6:    Animal()
  7:    {
  8:       cout << "Animal constructor" << endl;
  9:    }
 10:
 11:    // sample member
 12:    int age;
 13: };
 14:
 15: class Mammal:public Animal
 16: {
 17: };
 18:
 19: class Bird:public Animal
 20: {
 21: };
 22:
 23: class Reptile:public Animal
 24: {
 25: };
 26:
 27: class Platypus:public Mammal, public Bird, public Reptile
 28: {
 29: public:
 30:    Platypus()
 31:    {
 32:       cout << "Platypus constructor" << endl;
 33:    }
 34: };
 35:
 36: int main()
 37: {
 38:    Platypus duckBilledP;
 39:
 40:    // uncomment next line to see compile failure
 41:    // age is ambiguous as there are three instances of base Animal
 42:    // duckBilledP.age = 25;
 43:
 44:    return 0;
 45: }


Output Image

Animal constructor
Animal constructor
Animal constructor
Platypus constructor

Analysis Image

As the output demonstrates, due to multiple inheritance and all three base classes of Platypus inheriting in turn from class Animal, you have three instances of Animal created automatically for every instance of a Platypus, as shown in Line 38. This is ridiculous as Platypus is still one animal that has inherited certain attributes from Mammal, Bird, and Reptile. The problem in the number of instances of base Animal is not limited to memory consumption alone. Animal has an integer member Animal::age (that has been kept public for explanation purposes). When you want to access Animal::age via an instance of Platypus, as shown in Line 42, you get a compilation error simply because the compiler doesn’t know whether you want to set Mammal::Animal::age or Bird::Animal::age or Reptile::Animal::age. It can get even more ridiculous—if you so wanted you could set all three:

duckBilledP.Mammal::Animal::age = 25;
duckBilledP.Bird::Animal::age = 25;
duckBilledP.Reptile::Animal::age = 25;

Clearly, one duck-billed platypus should have only one age. Yet, you want class Platypus to be a Mammal, Bird, and Reptile. The solution is in virtual inheritance. If you expect a derived class to be used as a base class, it possibly is a good idea to define its relationship to the base using the keyword virtual:

class Derived1: public virtual Base
{
   // ... members and functions
};
class Derived2: public virtual Base
{
   // ... members and functions
};

A better class Platypus (actually a better class Mammal, class Bird, and class Reptile) is in Listing 11.8.

LISTING 11.8 Demonstrating How virtual Keyword in Inheritance Hierarchy Helps Restrict the Number of Instances of Base Class Animal to One


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Animal
  4: {
  5: public:
  6:    Animal()
  7:    {
  8:       cout << "Animal constructor" << endl;
  9:    }
 10:
 11:    // sample member
 12:    int age;
 13: };
 14:
 15: class Mammal:public virtual Animal
 16: {
 17: };
 18:
 19: class Bird:public virtual Animal
 20: {
 21: };
 22:
 23: class Reptile:public virtual Animal
 24: {
 25: };
 26:
 27: class Platypus final:public Mammal, public Bird, public Reptile
 28: {
 29: public:
 30:    Platypus()
 31:    {
 32:       cout << "Platypus constructor" << endl;
 33:    }
 34: };
 35:
 36: int main()
 37: {
 38:    Platypus duckBilledP;
 39:
 40:    // no compile error as there is only one Animal::age
 41:    duckBilledP.age = 25;
 42:
 43:    return 0;
 44: }


Output Image

Animal constructor
Platypus constructor

Analysis Image

Do a quick comparison against the output of the previous Listing 11.7, and you see that the number of instances of class Animal constructed has fallen to one, which is finally reflective of the fact that only one Platypus has been constructed as well. This is because of the keyword virtual used in the relationship between classes Mammal, Bird, and Reptile ensures that when these classes are grouped together under Platypus the common base Animal exists only in a single instance. This solves a lot of problems; one among them is Line 41 that now compiles without ambiguity resolution as shown in Listing 11.7. Also note the usage of keyword final in Line 27 to ensure that class Platypus cannot be used as a base class.


Note

Problems caused in an inheritance hierarchy containing two or more base classes that inherit from a common base, which results in the need for ambiguity resolution in the absence of virtual inheritance, is called the Diamond Problem.

The name “Diamond” is possibly inspired by the shape the class diagram takes (visualize Figure 11.2 with straight and slanted lines relating Platypus to Animal via Mammal, Bird, and Reptile to see a diamond).



Note

The virtual keyword in C++ often is used in different contexts for different purposes. (My best guess is that someone wanted to save time on inventing an apt keyword.) Here is a summary:

A function declared virtual means that an existing overriding function in a derived class is invoked.

An inheritance relationship declared using keyword virtual between classes Derived1 and Derived2 that inherits from class Base means that another class Derived3 that inherits from Derived1 and Derived2 still results in the creation of only one instance of Base during instantiation of type Derived3.

Thus the same keyword virtual is used to implement two different concepts.


Specifier Override to Indicate Intention to Override

Our versions of base class Fish have featured a virtual function called Swim() as seen in the following code:

class Fish
{
public:
   virtual void Swim()
   {
      cout << "Fish swims!" << endl;
   }
};

Assume that derived class Tuna were to define a function Swim() but with a slightly different signature—one using const inserted unintentionally by a programmer who wants to override Fish::Swim():

class Tuna:public Fish
{
public:
   void Swim() const
   {
      cout << "Tuna swims!" << endl;
   }
};

This function Tuna::Swim() actually does not override Fish::Swim(). The signatures are different thanks to the presence of const in Tuna::Swim(). Compilation succeeds, however, and the programmer may falsely believe that he has successfully overridden the function Swim() in class Tuna. C++11 and beyond give the programmer a specifier override that is used to verify whether the function being overridden has been declared as virtual by the base class:

class Tuna:public Fish
{
public:
   void Swim() const override // Error: no virtual fn with this sig in Fish
   {
      cout << "Tuna swims!" << endl;
   }
};

Thus, override supplies a powerful way of expressing the explicit intention to override a base class virtual function, thereby getting the compiler to check whether

Image The base class function is virtual.

Image The signature of the base class virtual function exactly matches the signature of the derived class function declared to override.

Use final to Prevent Function Overriding

Specifier final, introduced in C++11, was first presented to you in Lesson 10. A class declared as final cannot be used as a base class. Similarly, a virtual function declared as final cannot be overridden in a derived class.

Thus, a version of class Tuna that doesn’t allow any further specialization of virtual function Swim() would look like this:

class Tuna:public Fish
{
public:
   // override Fish::Swim and make this final
   void Swim() override final
   {
      cout << "Tuna swims!" << endl;
   }
};

This version of Tuna can be inherited from, but Swim() cannot be overridden any further:

class BluefinTuna final:public Tuna
{
public:
   void Swim() // Error: Swim() was final in Tuna, cannot override
   {
   }
};

A demonstration of specifiers override and final is available in Listing 11.9.


Note

We used final in the declaration of class BluefinTuna as well. This ensures that class BluefinTuna cannot be used as a base class. Therefore, the following would result in error:

class FailedDerivation:public BluefinTuna
{
};


Virtual Copy Constructors?

Well, the question mark at the end of the section title is justified. It is technically impossible in C++ to have virtual copy constructors. Yet, such a feature would help you create a collection (for example, a static array) of type Base*, each element being a specialization of that type:

// Tuna, Carp and Trout are classes that inherit public from base class Fish
Fish* pFishes[3];
Fishes[0] = new Tuna();
Fishes[1] = new Carp();
Fishes[2] = new Trout();

Then assigning it into another array of the same type, where the virtual copy constructor ensures a deep copy of the derived class objects as well, ensures that Tuna, Carp, and Trout are copied as Tuna, Carp, and Trout even though the copy constructor is operating on type Fish*.

Well, that’s a nice dream.

Virtual copy constructors are not possible because the virtual keyword in context of base class methods being overridden by implementations available in the derived class are about polymorphic behavior generated at runtime. Constructors, on the other hand, are not polymorphic in nature as they can construct only a fixed type, and hence C++ does not allow usage of the virtual copy constructors.

Having said that, there is a nice workaround in the form of defining your own clone function that allows you to do just that:

class Fish
{
public:
   virtual Fish* Clone() const = 0; // pure virtual function
};

class Tuna:public Fish
{
// ... other members
public:
   Tuna * Clone() const  // virtual clone function
   {
      return new Tuna(*this);  // return new Tuna that is a copy of this
   }
};

Thus, virtual function Clone is a simulated virtual copy constructor that needs to be explicitly invoked, as shown in Listing 11.9.

LISTING 11.9 Tuna and Carp That Support a Clone Function as a Simulated Virtual Copy Constructor


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Fish
  4: {
  5: public:
  6:    virtual Fish* Clone() = 0;
  7:    virtual void Swim() = 0;
  8:    virtual ~Fish() {};
  9: };
 10:
 11: class Tuna: public Fish
 12: {
 13: public:
 14:    Fish* Clone() override
 15:    {
 16:       return new Tuna (*this);
 17:    }
 18:
 19:    void Swim() override final
 20:    {
 21:       cout << "Tuna swims fast in the sea" << endl;
 22:    }
 23: };
 24:
 25: class BluefinTuna final:public Tuna
 26: {
 27: public:
 28:    Fish* Clone() override
 29:    {
 30:       return new BluefinTuna(*this);
 31:    }
 32:
 33:    // Cannot override Tuna::Swim as it is "final" in Tuna
 34: };
 35:
 36: class Carp final: public Fish
 37: {
 38:    Fish* Clone() override
 39:    {
 40:       return new Carp(*this);
 41:    }
 42:    void Swim() override final
 43:    {
 44:       cout << "Carp swims slow in the lake" << endl;
 45:    }
 46: };
 47:
 48: int main()
 49: {
 50:    const int ARRAY_SIZE = 4;
 51:
 52:    Fish* myFishes[ARRAY_SIZE] = {NULL};
 53:    myFishes[0] = new Tuna();
 54:    myFishes[1] = new Carp();
 55:    myFishes[2] = new BluefinTuna();
 56:    myFishes[3] = new Carp();
 57:
 58:    Fish* myNewFishes[ARRAY_SIZE];
 59:    for (int index = 0; index < ARRAY_SIZE; ++index)
 60:       myNewFishes[index] = myFishes[index]->Clone();
 61:
 62:    // invoke a virtual method to check
 63:    for (int index = 0; index < ARRAY_SIZE; ++index)
 64:       myNewFishes[index]->Swim();
 65:
 66:    // memory cleanup
 67:    for (int index = 0; index < ARRAY_SIZE; ++index)
 68:    {
 69:       delete myFishes[index];
 70:       delete myNewFishes[index];
 71:    }
 72:
 73:    return 0;
 74: }


Output Image

Tuna swims fast in the sea
Carp swims slow in the lake
Tuna swims fast in the sea
Carp swims slow in the lake

Analysis Image

In addition to demonstrating virtual copy constructors via virtual function Fish::Clone(), Listing 11.9 also demonstrates the usage of keywords override and final—the latter being applied to virtual functions and classes alike. It also features a virtual destructor for class Fish in Line 8. Lines 52–56 in main() demonstrate how a static array of pointers to base class Fish* has been declared and individual elements assigned to newly created objects of type Tuna, Carp, Tuna, and Carp, respectively. Note how this array myFishes is able to collect seemingly different types that are all related by a common base type Fish. This is already cool, if you compare it against previous arrays in this book that have mostly been of a simple monotonous type int. If that wasn’t cool enough, you were able to copy into a new array of type Fish* called myNewFishes using the virtual function Fish::Clone() within a loop, as shown in Line 60. Note that your array is quite small at only four elements. It could’ve been a lot longer but wouldn’t have made much of a difference to the copy logic that would only need to adjust the loop-ending condition parameter. Line 64 is the actual check where you invoke virtual function Fish::Swim() on each stored element in the new array to verify whether Clone() copied a Tuna as a Tuna and not just a Fish(). The output demonstrates that it genuinely did copy the Tunas and the Carps just as expected. Also note that the output of Swim() used on instance of BluefinTuna was the same as that for a Tuna, because Tuna::Swim() was declared as final. Thus, BluefinTuna was not permitted to override Swim(), and the compiler executed Tuna::Swim() for it.

Summary

In this lesson you learned to tap the power of creating inheritance hierarchies in your C++ code, by using polymorphism. You learned how to declare and program virtual functions—how they ensure that the derived class implementation overrides that in the base class even if an instance of the base class is used to invoke the virtual method. You saw how pure virtual functions were a special type of virtual functions that ensure that the base class alone cannot be instantiated, making it a perfect place to define interfaces that derived classes must fulfill. Finally, you saw the Diamond Problem created by multiple inheritance and how virtual inheritance helps you solve it.

Q&A

Q Why use the virtual keyword with a base class function when code compiles without it?

A Without the virtual keyword, you are not able to ensure that someone calling objBase.Function() will be redirected to Derived::Function(). Besides, compilation of code is not the only measure of its quality.

Q Why did the compiler create the Virtual Function Table?

A To store function pointers that ensure that the right version of a virtual function is invoked.

Q Should a base class always have a virtual destructor?

A Ideally yes. Only then can you ensure that when someone does a

Base* pBase = new Derived();
delete pBase;

delete on a pointer of type Base* results in the destructor ~Derived() being invoked. This occurs when destructor ~Base() is declared virtual.

Q What is an Abstract Base Class good for when I can’t even instantiate it standalone?

A The ABC is not meant to be instantiated as a standalone object; rather it is always meant to be derived from. It contains pure virtual functions that define the minimal blueprint of functions that deriving classes need to implement, thus taking the role of an interface.

Q Given an inheritance hierarchy, do I need to use the keyword virtual on all declarations of a virtual function or just in the base class?

A It is enough to declare a function as virtual once, but that declaration has to be in the base class.

Q Can I define member functions and have member attributes in an ABC?

A Sure you can. Remember that you still cannot instantiate an ABC as it has at least one pure virtual function that needs to be implemented by a deriving class.

Workshop

The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you’ve learned. Try to answer the quiz and exercise questions before checking the answers in Appendix E, and be certain you understand the answers before continuing to the next lesson.

Quiz

1. You are modeling shapes—circle and triangle—and want every shape to compulsorily implement functions Area() and Print(). How would you do it?

2. Does a compiler create a Virtual Function Table for all classes?

3. My class Fish has two public methods, one pure virtual function, and some member attributes. Is it still an abstract base class?

Exercises

1. Demonstrate an inheritance hierarchy that implements the question in Quiz 1 for Circle and Triangle.

2. BUG BUSTERS: What is the problem in the following code:

class Vehicle
{
public:
   Vehicle() {}
   ~Vehicle(){}
};
class Car: public Vehicle
{
public:
   Car() {}
   ~Car() {}
};

3. In the (uncorrected) code in Exercise 2, what is the order of execution of constructors and destructors if an instance of car is created and destroyed like this:

Vehicle* pMyRacer = new Car;
delete pMyRacer;

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.4.181