Understanding Polymorphism in C++

Polymorphism gives flexibility to a program. To understand how polymorphism provides flexibility to your program, let's start off with a brief introduction to Abstract Classes.

An abstract class is a class that acts as a legal contract. The legal contract states that if you are a subclass of an abstract class, you must implement every method of the abstract class.

An abstract class has the following properties:

  1. It does not have a constructor method.
  2. Its destructor must be declared as a virtual method.
  3. Since it does not have a constructor, you can't create instances of an abstract class.
  4. Methods in an abstract class must contain the keyword 'virtual' in their declaration
  5. Methods in a virtual class are always set to '0.'

The snippet below declares an abstract class called "Dog."

class Dog{

private:

public:

    virtual void run()=0; //1. Virtual Run method

    virtual ~Dog(){}; //2. Virtual destructor

};

Line 1 declares the virtual 'run' method. Line 2 declares the class virtual destructor. Notice that these methods are declared within the public interface. A different name for an abstract class is an Interface.

Since an instance of an abstract class is not possible, an abstract class requires subclasses to be useful. As stated earlier, the subclass must implement every method of the abstract class.

The snippet below declares the subclass 'Beagle.' The implementation of the 'run' method is shown in line 5.

class Beagle:public Dog{

private:

public:
    Beagle(); //1. Constructor

    ~Beagle(); //2. Destructor

    void run(); //3. Implement the run method

};

Beagle::Beagle(){}

Beagle::~Beagle(){}

void Beagle::run(){

    std::cout<<"I run like a beagle"<<std::endl; //5. Print "I run like a beagle"

}

In practice, you would create an instance of the 'Beagle' subclass as shown in the snippet below:

int main(){

    Beagle *myBeagle=new Beagle(); //1. Creating an instance of the subclass

    return 0;
}

However, you shouldn't create the instance as shown above. Instead, you should create the instance as shown below:

int main(){

    Dog *myBeagle=new Beagle(); //1. Code to the interface, not the implementation

    return 0;
}

Notice that myBeagle points to the abstract class type. It does not point to the subclass type. This approach is known as "Code to the interface, not to the implementation" and is one of the 7 Principles of Object-Oriented Programming. In the statement above, "Interface" refers to an Abstract Class. "Implementation" refers to a Subclass.

"Code to the interface, not to the implementation" injects polymorphism into your project, thus making it flexible.

To see polymorphism in action, let's create a subclass call 'Pitbull' as shown below:

class Pitbull:public Dog{

private:

public:
    Pitbull(); //1. Constructor

    ~Pitbull(); //2. Destructor

    void run(); //3. Implement the run method

};

Pitbull::Pitbull(){}

Pitbull::~Pitbull(){}

void Pitbull::run(){

    std::cout<<"I run like a pitbull"<<std::endl; //5. Print "I run like a pitbull"

}

As required, the subclass implemented the 'run' method (line 5).

Let's create a function called 'youRunLike' as shown below. Notice the argument of the function is of type 'Dog,' the abstract class.

void getRunSpeed(Dog *uDog); //1. Function declaration

void getRunSpeed(Dog *uDog){ //2. Function definition

    uDog->run(); //3. Call the run method

}

Notice the call to the 'run' method of the 'Dog' class (line 3). This is a bit weird. The abstract class never implemented this method, only its subclasses have. How does it know which subclass to call?

Polymorphism allows the function to use the same argument for either subclass type, 'Beagle' or 'Pitbull' and it will direct the call to the correct 'run' method. You do not need to create different functions with different argument types.

For example, in the snippet below, lines 1 & 2 creates the instances of the subclasses using the "Code to the interface, not to the implementation" approach. Lines 3 & 4 calls the function 'youRunLike.'

int main(){

    Dog *myBeagle=new Beagle(); //1. Instance of Beagle

    Dog *myPitbull=new Pitbull; //2. Instance of Pitbull

    getRunSpeed(myBeagle); //3. Call the function-It prints "I run like a beagle"

    getRunSpeed(myPitbull); //4. Call the function- It prints "I run like a pitbull"

    return 0;
}

When you execute the snippet above, line 3 will print 'I run like a beagle.' Whereas, line 4 will print 'I run like a Pitbull.' As expected, the correct 'run' method for each subclass was evoked.

Polymorphism gives flexibility and modularity to an application. If 100 years from now, a new subclass is defined, it will be able to use the same 'youRunLike' function without any modifications.

Hope this helps

Harold Serrano

Computer Graphics Enthusiast. Currently developing a 3D Game Engine.