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 }67 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 Class2class Monster {3 public:4 void setHealth(int hearts) {5 this->hearts = hearts;6 }78 int getHealth() {9 return this->hearts;10 }1112 void SelfDestruct() {13 cout << "Monster go bye-bye" << endl;14 this->hearts = 0;15 }1617 protected:18 int hearts;19}202122// Derived Class23class Wumpus : public Monster {24 public:25 void Hide() {26 cout << "Shhhhhhh" << endl;27 this->isVisible = false;28 }2930 void Appear() {31 cout << "ARGHHARHGHHHH" << endl;32 this->isVisible = true;33 }3435 void SelfDestruct() {36 cout << "GG, Wumpus is outta here" << endl;37 this->hearts = 2;38 }3940 private:41 bool isVisible;42}4344int main() {45 Monster* base;46 Wumpus* derived;4748 vector<Monster*> monstars;4950 monstars.push_back(base);51 monstars.push_back(derived);5253 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-poly2Monster go bye-bye3
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 Class2class Monster {3 public:4 void setHealth(int hearts) {5 this->hearts = hearts;6 }78 int getHealth() {9 return this->hearts;10 }1112 virtual void SelfDestruct() {13 cout << "Monster go bye-bye" << endl;14 this->hearts = 0;15 }1617 protected:18 int hearts;19}202122// Derived Class23class Wumpus : public Monster {24 public:25 void Hide() {26 cout << "Shhhhhhh" << endl;27 this->isVisible = false;28 }2930 void Appear() {31 cout << "ARGHHARHGHHHH" << endl;32 this->isVisible = true;33 }3435 void SelfDestruct() override {36 cout << "GG, Wumpus is outta here" << endl;37 this->hearts = 2;38 }3940 private:41 bool isVisible;42}4344int main() {45 Monster* base;46 Wumpus* derived;4748 vector<Monster*> monstars;4950 monstars.push_back(base);51 monstars.push_back(derived);5253 base = &derived;54 cout << base->SelfDestruct() << endl;55 return 0;56}57
This will output:
1./updated-runtime-poly2GG, Wumpus is outta here3
The virtual
and override
keywords should be used:
keyword | Where To Use It |
---|---|
virtual | base class method that is being overridden by a subclass |
override | derived 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;45 protected:6 int health;7}89class 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
- C++ Inheritance: Subclass Definition (Pt. 1)
- C++ Inheritance: Protected Member Access (Pt. 2)
- C++ Inheritance: Inheritance Access Levels (Pt. 3)
- C++ Inheritance: Overriding Base Class Methods (Pt. 4)
Written by Riley Miller, follow me on Twitter 🐦