Riley Miller

C++ Inheritance: Polymorphism (Pt. 5)

July 07, 2020

A key facet of inheritance in C++ is polymorphism, where execution can vary based off of data type. This becomes particulary useful when defining subclasses that require different functionality than the parent class.

In C++ there are two types of polymorphism, compile-time polymorhpism and run-time polymorphism. Both of these terms refer to when the compiler is able to determine which function to call.

Compile-time polymorphism is typically affiliated with method overriding where the compiler is able to determine which method to invoke when there are identical methods that have different paramters. Ex:

1class Monster {
2 public:
3 void Scream() {
4 cout << "AHDSFDSFSD" << endl;
5 }
6
7 void Scream(int n) {
8 cout << "IM SCARY X" << n << endl;
9 }
10}
11

In the example above, the C++ compiler is smart enough to know that if a Monster object calls the Scream method without passing in any parameters, it will call the first implementation, but if it calls Scream(3) it will invoke the second implementation of Scream because the method is taking in an integer parameter.

Which leads us to run-time polymorphism, where the compiler isn’t able to determine which function to call during compilation. The run-time style of polymorphism is commonly seen in practice when declaring containers that can hold instances of a base class and its subclasses. For example:

1// The Base Class
2class Monster {
3 public:
4 void setHealth(int hearts) {
5 this->hearts = hearts;
6 }
7
8 int getHealth() {
9 return this->hearts;
10 }
11
12 void SelfDestruct() {
13 cout << "Monster go bye-bye" << endl;
14 this->hearts = 0;
15 }
16
17 protected:
18 int hearts;
19}
20
21
22// Derived Class
23class Wumpus : public Monster {
24 public:
25 void Hide() {
26 cout << "Shhhhhhh" << endl;
27 this->isVisible = false;
28 }
29
30 void Appear() {
31 cout << "ARGHHARHGHHHH" << endl;
32 this->isVisible = true;
33 }
34
35 void SelfDestruct() {
36 cout << "GG, Wumpus is outta here" << endl;
37 this->hearts = 2;
38 }
39
40 private:
41 bool isVisible;
42}
43
44int main() {
45 Monster* base;
46 Wumpus* derived;
47
48 vector<Monster*> monstars;
49
50 monstars.push_back(base);
51 monstars.push_back(derived);
52
53 base = &derived;
54 cout << base->SelfDestruct() << endl;
55 return 0;
56}
57

In the main() method you can see that we’re able to add a pointer to a Wumpus class to a Monster * vector. This is made possible by a C++ feature called base/derived class pointer conversion, where the language is able to impicitly cast a derived class pointer to a pointer of the base class without any explicit type casting. However, now that the Wumpus pointer has been cast to a Monster pointer, the program would need a way to tell which implementation of the SelfDestruct method to call for both the Wumpus * and Monster * during execution. Enter *virtual functions**.

Virtual Functions

In order to distinguish which implementation of SelfDestruct to call during the program execution, C++ exposes some interesting language features for us to enable run-time polymorphism. Specifically, the virtual keyword which can be preprended to a method declaration in the base class which signifies that any subclass definition can override this method and then when the virtual function is called during execution, the program can determine which function to call between the base class and the derived class based off the type of the object.

Now, if we revisit the example above, when we try to call the SelfDestruct method of the reference to the Wumpus object. The program will actually output the Monster classes implementation of the SelfDestruct() method.. not the Wumpus implementation. Output:

1$ ./runtime-poly
2Monster go bye-bye
3

In order to enable run-time polymorphism, we need to allow the subclass to override the base class using the virtual keyword.

Another useful keyword that is used with polymorphism in C++ is override. The override keyword is optional, and is appended to the method declaration. The override keyword gives you typechecking when overriding a base class virtual method so that you don’t accidentally mispell the method name or specify incorrect parameters to the function.

Here is an updated version of the example above using the virtual/override implementation so that we can dynamically specify the correct method at run-time.

Example:

1// The Base Class
2class Monster {
3 public:
4 void setHealth(int hearts) {
5 this->hearts = hearts;
6 }
7
8 int getHealth() {
9 return this->hearts;
10 }
11
12 virtual void SelfDestruct() {
13 cout << "Monster go bye-bye" << endl;
14 this->hearts = 0;
15 }
16
17 protected:
18 int hearts;
19}
20
21
22// Derived Class
23class Wumpus : public Monster {
24 public:
25 void Hide() {
26 cout << "Shhhhhhh" << endl;
27 this->isVisible = false;
28 }
29
30 void Appear() {
31 cout << "ARGHHARHGHHHH" << endl;
32 this->isVisible = true;
33 }
34
35 void SelfDestruct() override {
36 cout << "GG, Wumpus is outta here" << endl;
37 this->hearts = 2;
38 }
39
40 private:
41 bool isVisible;
42}
43
44int main() {
45 Monster* base;
46 Wumpus* derived;
47
48 vector<Monster*> monstars;
49
50 monstars.push_back(base);
51 monstars.push_back(derived);
52
53 base = &derived;
54 cout << base->SelfDestruct() << endl;
55 return 0;
56}
57

This will output:

1./updated-runtime-poly
2GG, Wumpus is outta here
3

The virtual and override keywords should be used:

keywordWhere To Use It
virtualbase class method that is being overridden by a subclass
overridederived class method that is overriding the base class method

Pure Virtual Functions

Sometimes a base class will not specify an implementation for a specific method and will instead rely on the subclass define this method in its own class definition. These functions are called pure virtual function and they are specified by appending const = 0; after the method name. Example:

1class Monster {
2 public:
3 virtual void Dance() const = 0;
4
5 protected:
6 int health;
7}
8
9class Wumpus : public Monster {
10 public:
11 void Dance() const override {
12 cout << "Wumpus getting groovy" << endl;
13 }
14}
15

IMPORTANT!! Whenever a base class implements a pure virtual method, this class cannot be instantiated as an object, this class is called an abstract base class and is used to define a set of common attributes and methods that each subclass should define.

C++ Inheritance Articles


Written by Riley Miller, follow me on Twitter 🐦

//