Polymorphism

Polymorphism is one of the most important features of inheritance, but it’s fairly challenging to describe without showing some examples. To start, let’s go back to using examples related to robotics programming.

You’ll see later that it can be helpful to group robot code into Command classes, where Command is a base class that has multiple derived classes representing different types of commands. For example, there may be a MoveForward command, or a Rotate command, both of which inherit from the Command class. Here’s what that could look like:

#include <iostream>

class Command {
    public:
        virtual void run() = 0;
};

class MoveForward : public Command {
    private:
        float distance_ft;

    public:
        MoveForward(float distance_ft) {
            this->distance_ft = distance_ft;
        }
        void run() override {
            std::cout << "Moving forward " << distance_ft << " feet...\n";
        }
};

class Rotate : public Command {
    private:
        float angle_deg;

    public:
        Rotate(float angle_deg) {
            this->angle_deg = angle_deg;
        }
        void run() override {
            std::cout << "Rotating " << angle_deg << " degrees...\n";
        }
};

int main() {
    MoveForward move(2.5);
    Rotate rotate(90);

    Command *moveCommand = &move;
    Command *rotateCommand = &rotate;

    moveCommand->run();
    rotateCommand->run();

    return 0;
}

Program output:

Moving forward 2.5 feet...
Rotating 90 degrees...

Now, this code uses polymorphism, so I’ll start by showing what polymorphism is and how it works. After that, I’ll show you when it’s useful.

Let’s start in the main function:

  • We first create instances of the MoveForward and Rotate classes, just like we’ve done previously.
    • The move object is constructed with a distance_ft value of 2.5, and the rotate object is constructed with an angle_deg value of 90.
  • The next two lines are where polymorphism comes into play.
    • Polymorphism allows you to represent the data type of an object as a pointer to the object’s base class. This means we can create the pointer “moveCommand” with an underlying data type of “Command“, even though the move object actually has the data type “MoveForward“. This works because MoveForward is a Command. The same applies to the rotate object.
      • Note that polymorphism only works when using pointers (or references).
  • Lastly, we use the run function on both of the Command pointers.
    • Here, you may see that the run function inside the Command class doesn’t really do much. When using polymorphism, the function used depends on the derived class that the pointer represents. Therefore, moveCommand->run(); calls the run function from the MoveForward class, not from the Command class.

Now let’s look at the code in each of the classes:

  • The Command class contains a function called “run” that looks quite a bit different from what we’ve seen before.
    • The word “virtual” means that the function can be “overriden” by functions in derived classes.
      • You can see that both the MoveForward and the Rotate classes have their own run functions with the word override after their parentheses. This means that these two functions “override” (replace) the run function in their base class (the Command class).
    • The “ = 0;” following the Command class’s run function turns it into a “pure virtual function“. This means that the function has no “implementation” (functionality), so it can never be called. This is fine, though, because the function is overriden in its two derived classes.
      • This function is made a pure virtual function because it doesn’t make sense to “run” just a “Command“. Running the MoveForward or Rotate commands, however, makes sense because those commands perform a specific task.
      • Any class that has at least one pure virtual function is called an “abstract class.” Abstract class instances are not allowed (so I cannot create a Command object). However, as you saw in the main function, abstract class pointers are allowed, as long as they point to an object of a non-abstract derived class (such as MoveForward or Rotate).
So what?

A normal initial reaction to polymorphism is thinking that it’s overly complex and doesn’t provide any benefits (at least that’s what I first thought).

Generally, polymorphism is useful because it allows us to store objects of different (but related) data types all in one place (e.g. an array or vector). By using polymorphism with robot commands, we can store a sequence of different commands all inside a single vector. This wouldn’t work without polymorphism because vectors can only store values of a single data type. With polymorphism, that single data type is a base class pointer:

#include <iostream>
#include <vector>

class Command {
    public:
        virtual void run() = 0;
};

class MoveForward : public Command {
    private:
        float distance_ft;

    public:
        MoveForward(float distance_ft) {
            this->distance_ft = distance_ft;
        }
        void run() override {
            std::cout << "Moving forward " << distance_ft << " feet...\n";
        }
};

class Rotate : public Command {
    private:
        float angle_deg;

    public:
        Rotate(float angle_deg) {
            this->angle_deg = angle_deg;
        }
        void run() override {
            std::cout << "Rotating " << angle_deg << " degrees...\n";
        }
};

int main() {
    MoveForward move(2.5);
    Rotate rotate(90);

    std::vector<Command*> commandSequence = {&move, &rotate};
    for (int i = 0; i < commandSequence.size(); i++) {
        commandSequence[i]->run();
    }

    return 0;
}

Program output:

Moving forward 2.5 feet...
Rotating 90 degrees...

When structuring our code like this, we can also easily add new commands to the commandSequence vector later on (if a button on a controller is pressed, for example). Of course, this only works if the command added inherits from the Command class.

A Deeper Look into “virtual

In case you don’t fully understand how the keyword “virtual” affects functions, let’s look at some other cases:

Non-Virtual Function in Base Class

By changing the Command class to this…

class Command {
    public:
        void run() {
            std::cout << "Default command running...\n";
        }
};

…the program won’t compile because the other two run functions use the “override” keyword. When a function in the base class is not virtual, the function cannot be overriden by derived classes.

Let’s remove the word “override” from the other classes and see what happens:

#include <iostream>
#include <vector>

class Command {
    public:
        void run() {
            std::cout << "Default command running...\n";
        }
};

class MoveForward : public Command {
    private:
        float distance_ft;

    public:
        MoveForward(float distance_ft) {
            this->distance_ft = distance_ft;
        }
        void run() {
            std::cout << "Moving forward " << distance_ft << " feet...\n";
        }
};

class Rotate : public Command {
    private:
        float angle_deg;

    public:
        Rotate(float angle_deg) {
            this->angle_deg = angle_deg;
        }
        void run() {
            std::cout << "Rotating " << angle_deg << " degrees...\n";
        }
};

int main() {
    MoveForward move(2.5);
    Rotate rotate(90);

    std::vector<Command*> commandSequence = {&move, &rotate};
    for (int i = 0; i < commandSequence.size(); i++) {
        commandSequence[i]->run();
    }

    return 0;
}

Program output:

Default command running...
Default command running...

Now the Command class’s run function is used because it is not overriden by its derived classes.

Virtual (but not Pure) Function in Base Class

Let’s now add the word “virtual” back to the run function in the Command class and add the word “override” back to the other two run functions. However, rather than making the Command class’s run function “pure virtual” (set equal to 0), let’s keep its implementation the same:

class Command {
    public:
        virtual void run() {
            std::cout << "Default command running...\n";
        }
};

Even though run is a virtual function, the Command class is not an abstract class in this case (because it doesn’t have at least one pure virtual function). This means that a Command object can be created:

#include <iostream>
#include <vector>

class Command {
    public:
        virtual void run() {
            std::cout << "Default command running...\n";
        }
};

class MoveForward : public Command {
    private:
        float distance_ft;

    public:
        MoveForward(float distance_ft) {
            this->distance_ft = distance_ft;
        }
        void run() override {
            std::cout << "Moving forward " << distance_ft << " feet...\n";
        }
};

class Rotate : public Command {
    private:
        float angle_deg;

    public:
        Rotate(float angle_deg) {
            this->angle_deg = angle_deg;
        }
        void run() override {
            std::cout << "Rotating " << angle_deg << " degrees...\n";
        }
};

int main() {
    MoveForward move(2.5);
    Rotate rotate(90);
    Command genericCommand;

    std::vector<Command*> commandSequence = {&move, &rotate, &genericCommand};
    for (int i = 0; i < commandSequence.size(); i++) {
        commandSequence[i]->run();
    }

    return 0;
}

Program output:

Moving forward 2.5 feet...
Rotating 90 degrees...
Default command running...

In this program, objects of the Command class can be created that have their own run function. Since that run function is virtual, however, it can be overriden by the other two classes.

Challenge Problem

Write a program that follows the specifications below:

  • Create a Shape class that is abstract
    • The class should contain a pure virtual function called “draw
  • Create an EquilateralTriangle class that inherits from Shape (using public inheritance).
    • The class should include a constructor and an override of the draw function.
    • The constructor should include one parameter for the triangle’s side length.
    • The draw function should print a triangle to the screen that corresponds to the triangle’s side length.
  • Create a Rectangle class that inherits from Shape (using public inheritance).
    • The class should include a constructor and an override of the draw function.
    • The constructor should include two parameters for the rectangle’s side lengths.
    • The draw function should print a rectangle to the screen that corresponds to the rectangle’s width and height.
  • Create a Square class that inherits from Rectangle (using public inheritance).
    • The class should include a constructor with one parameter for the square’s side length.
    • The constructor should also call Rectangle‘s constructor.
  • The main function should include a vector containing multiple shapes.
    • Loop through the vector and call the draw function for each shape.
  • Drawing the shapes could be done with any character you’d like (I chose the asterisk).

Sample output:

    * 
   * *
  * * *
 * * * *
* * * * *

* * * * * *
* * * * * *
* * * * * *
* * * * * *

* * * *
* * * *
* * * *
* * * *

This output was produced using a triangle with a side length of 5, a 4×6 rectangle, and a square with a side length of 4.

If you get stuck, check out my solution here.