Lesson 5
Polymorphism Basics in C++
Lesson Introduction

Polymorphism is a key concept in C++ that allows objects from different classes to be treated as objects of a shared base class. It makes your code more flexible and reusable. The term "polymorphism" stems from Greek words meaning "many forms."

Our goal in this lesson is to understand the basics of polymorphism in C++ by working with a chess example.

What is Polymorphism and Why Do We Need It?

Polymorphism allows you to design and implement programs that are more flexible and easier to extend over time. It enables one interface to be used for a general class of actions, with specific actions determined by the exact nature of the situation.

In C++, polymorphism is primarily achieved through the use of base class pointers or references to invoke methods in derived classes. This makes it possible to write code that can work with objects of different types and classes in a unified way. Sounds difficult? It really isn't! Let's see an example, it will help you understand the idea.

Define an Abstract Class: `ChessPiece`

An abstract class in C++ is a class that cannot be instantiated on its own and is designed to be inherited by other classes. It serves as a blueprint for derived classes. An abstract class typically includes one or more pure virtual functions.

A pure virtual function is a function declaration that is specified in the base class but has no implementation in that class. Instead, it must be implemented by any non-abstract derived class. This is done by assigning 0 to the function declaration in the base class.

Additionally, it's a good practice to have a virtual destructor in an abstract class. This ensures that the destructor for derived classes will be called correctly, preventing memory leaks.

We'll define an abstract class ChessPiece with a pure virtual function draw and a virtual destructor. This function will be overridden by derived classes.

C++
1#include <iostream> 2 3class ChessPiece { 4public: 5 virtual ~ChessPiece() {} // Virtual destructor 6 virtual void draw() const = 0; // Pure virtual function 7};

Here, ChessPiece serves as a blueprint for all chess pieces, and draw is a method that each piece must implement.

Note that you can't create an instance of a class having at least one pure virtual function, because this class is abstract. For example, this is not allowed:

C++
1int main() { 2 ChessPiece abstract_piece = new ChessPiece(); 3 abstract_piece->draw(); 4}

This code will cause an error.

Derive Classes `Pawn` and `Knight` from `ChessPiece`

Next, we'll create two derived classes, Pawn and Knight, which will implement the draw method.

C++
1class Pawn : public ChessPiece { 2public: 3 void draw() const override { 4 std::cout << "_Pawn_" << std::endl; 5 } 6}; 7 8class Knight : public ChessPiece { 9public: 10 void draw() const override { 11 std::cout << "_Knight_" << std::endl; 12 } 13};

Here, both classes Pawn and Knight provide their own implementations of the draw method.

Store`Pawn` Objects and `Knight` Objects in One Array

What if we want to create an array to store all the chess pieces?

Using pointers to the base class allows you to create an array capable of holding objects of any derived class. This is crucial because C++ is a statically-typed language, and the compiler needs to know the type and size of each array element at compile-time. By storing pointers to the base class, you enable the compiler to treat all array elements as having the same type—pointers to the base class—thus achieving polymorphic behavior.

Let's see an example:

C++
1int main() { 2 ChessPiece* pieces[10]; // Array of base class pointers 3 4 for (int i = 0; i < 8; ++i) { 5 pieces[i] = new Pawn(); 6 } 7 8 for (int i = 8; i < 10; ++i) { 9 pieces[i] = new Knight(); 10 }

Here, we create an array pieces of ChessPiece pointers. Both Pawn and Knight are derived from the ChessPiece, so both can be initialized with a ChessPiece* pointer. The code shows how to store 8 Pawn objects and 2 Knight objects in this array.

Call the `draw` Method for Each Object in the Array

Finally, we'll loop through the array and call the draw method on each object. This demonstrates polymorphic behavior.

C++
1 for (int i = 0; i < 10; ++i) { 2 pieces[i]->draw(); // Calls appropriate overridden function 3 } 4 5 for (int i = 0; i < 10; ++i) { 6 delete pieces[i]; // Clean up the heap-allocated memory 7 } 8 9 return 0; 10}

In this loop, each pieces[i]->draw() call invokes the draw method for the appropriate Pawn or Knight object. This way, we have a uniform code to work with each object. You can add more pieces of different types, and you won't need to modify this draw loop – it will automatically call the appropriate draw method for each piece.

Non-abstract Base Class

Polymorphism works even when the base class is not abstract. Let's take a look at this example:

C++
1class ChessPiece { 2public: 3 virtual void draw() { 4 std::cout << "Can't draw an abstract piece!" << std::endl; 5 } 6 7 virtual ~ChessPiece() {} // Virtual destructor 8}; 9 10class Pawn : public ChessPiece { 11public: 12 void draw() { 13 std::cout << "_Pawn_" << std::endl; 14 } 15}; 16 17class Knight : public ChessPiece { 18public: 19 void draw() { 20 std::cout << "_Knight_" << std::endl; 21 } 22};

It will work in exactly the same way. But in this case, the ChessPiece class is not abstract, as its only method, draw, is defined. We can create instances of the ChessPiece class and operate them.

Lesson Summary

Congratulations! You've learned how to apply polymorphism in C++ using a practical example:

  • Define an abstract class with a pure virtual function.
  • Create derived classes that implement this pure virtual function.
  • Store multiple derived class objects in a single array using base class pointers.
  • Invoke methods on these objects to demonstrate polymorphism.

It's time for practice! Move on to exercises where you'll use polymorphism with virtual functions and abstract classes in C++. These will solidify your understanding and give you hands-on experience. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.