Polymorphism in C++

Polymorphism in C++ is one of the core and important features. The polymorphism concept describes ways in which one can access objects/functions of different types with the same interface.

What is Polymorphism in C++?

Polymorphism in C++ literally means the condition occurring in several different forms. In C++, Polymorphism occurs when the same entity behaves differently in different contexts. Let’s take an example:

class Animal{
  public:
    void sound(){
      cout << "Returns the sound the animal makes" << endl;
    }
 };
 
class Cow : public Animal{
  public:
    void sound(){
      cout << "MOOW" << endl;
    }
 };

 class Cat : public Animal{
  public:
    void sound(){
      cout << "MEOW" << endl;
    }
 };

Real-Life Example of Polymorphism

Suppose you are looking for an Insurance plan and you open a website that compares different insurance plans and shows you the premium. Here we can have a base class called Insurance which has a member function that calculates and returns the premium. There are many different Insurance providers like SBI, HDFC Life, Max Life, Bajaj alliance etc., all these insurance providers have different premium rates and have different implementations with the same function name. This is also called function overriding which we learn in further sections.

polymorphism-example

Types of Polymorphism in C++

C++ supports two types of polymorphism namely Compile-Time Polymorphism and Run-Time Polymorphism. These can further be categorized as follows:

types-of-polymorphism

Compile-Time Polymorphism in C++

When a function is called during program compilation it is called Compile-Time Polymorphism. It is also called as static binding or early binding as definitions of the functions are resolved at the compilation time as part of static code.

Function Overloading

Function overloading occurs when many functions have the same name but different types of arguments and return types. C++ supports function overloading and the correct definition is picked based on the tyes/number of arguments and return type during the compile time. Here is a small example where function overloading can be used:

class Calculator{
  public:
    int add(int a, int b){
      return a + b;
    }
    
    int add(int a, int b, int c){
      return a + b + c;
    }

    float add(float a, float b){
      return a + b;
    }
};

In the above example function add() takes different forms with different number of inputs and different data types.

Operator overloading

Operator overloading occurs when the functionality of the operator is extended without changing the meaning of the operation. C++ supports operator overloading and the overloaded operation picks the definition of the operation at the compile time. Here is an example with arthematic operations of complex numbers:

class Complex{
  private:
    float real, img;
  public:
    Complex(float r, float i){
      real = r;
      img = i;
    }

    Complex operator+ (Complex A, Complex B){
      Complex C;
      C.real = A.real + B.real;
      C.img = A.img + B.img;
      return C;
    }

    void display(){
      cout << real << " + i " << img << endl;
    } 
};

int main(){
  Complex A(2, 3);
  Complex B(5, 6);
  Complex C = A + B;
  C.display();

  return 0;
}

In the above example when Complex C = A + B; is compiled the definition operator+ () will be picked and the addition of two complex numbers will possible.

Run-Time Polymorphism in C++

When the function is called at the run-time of the program it is called Run-Time Polymorphism. It is also referred as the late binding or dynamic binding as the definition of the function is resolved during the run-time as part of dynamic code.

Function overriding

As the name suggests the Function overriding occurs when function is redefined or re-implemented. In C++ we can override the functions of base class in derived class. Here is an example for function overriding with Mammals eating habits:

class Mammal{
  public:
    void eats(){
      cout << "Mammals eat animal and plant based foods" << endl;
    }
};

class Cow : public Mammal{
  public:
    void eats(){
      cout << "Cow eats grass and plants" << endl;
    }
};

class Tiger : public Mammal{
  public:
    void eats(){
      cout << "Tiger eats herbivores animals" << endl;
    }
};

class Human : public Mammal{
  public:
    void eats(){
      cout << "Human eats everything!" << endl;
    }
};

In the above example the base class Mammal implements a function eats() which is overridden by derived classes based on the eating habits respectively.

Virtual Functions

Functions written in the base class with the keyword virtual are called virtual functions. The virtual keyword ensures that the function is overridden in the derived class. Virtual functions help compiler to perform the dynamic binding of the functions. If virtual functions are not overridden in the derived class compiler will through an error.

When ever a virtual function is created there is a corresponding VTable or Virtual-Table generated by the compiler which acts dynamic binding link and resolves the function pointer to the appropriate definition during run-time.

Here it is important to note that a function cannot be both virtual and static at the same time.

Let’s take same example of Mammal class but now with virtual functions:

class Mammal{
  public:
    virtual void eats(){
      cout << "Mammals eat animal and plant based foods" << endl;
    }
};

class Cow : public Mammal{
  public:
    void eats(){
      cout << "Cow eats grass and plants" << endl;
    }
};

class Tiger : public Mammal{
  public:
    void eats(){
      cout << "Tiger eats herbivores animals" << endl;
    }
};

In the above example the base class Mammal implements a virtual function eats() which is overridden by derived classes.

Pure Virtual Functions

Pure virtual functions are the functions with no implementations. These are also called as ‘Do-nothing functions’. A class with at least one pure virtual function is called an abstract class and when all the functions of a class are pure virtual it is called interface class.

Let’s see an example of pure virtual function for the display of notifications in the phone:

class Notification{
  public:
    virtual void display() = 0;
};

class AlertNotification : public Notification{
  public:
    void display(){
      cout << "Displays notification with priority using sound and vibration" << endl;
    }
};

class SilentNotification : public Notification{
  public:
    void display(){
      cout << "Displays notification with lower priority not using sound or vibration" << endl;
    }
};

In the above example the base class has a pure virtual function which needs a definition in the derived class without which compiler will throw errors. Here the derived classes AlertNotification and SilentNotification implement their own version of display() So that we can use base class Notification pointer to call all the derived classes and have correct function resolutions at run-time.

int main(){
  Notification* M = new AlertNotification();
  Notification* N = new SilentNotification();
  cout << M << " : ";
  M->display();
  cout << N << " : ";
  N->display();
  return 0;
}


_______________________________________________________
OUTPUT:
0x55c9b9234eb0 : Displays notification with priority using sound and vibration
 0x55c9b9234ed0 : Displays notification with lower priority not using sound or vibration

If you closely observe the output of the above example you will see that the class pointer for both M and N point to the same location because of the same Base class Notification. During the run-time the function call to display() would be resolved with help of VTable and the correct output will be printed.

Compile-Time Polymorphism V/S Run-Time Polymorphism

Compile-Time PolymorphismRun-Time Polymorphism
Also called early-binding or static bindingAlso called lazy binding or dynamic binding
The method is invoked at the compile timeThe method is invoked at the run-time
Achieved using function overloading and operator overloading features of C++Achieved using function overriding and virtual functions feature of C++
Less flexibility as everything has to be known at the compile timeMore flexibility as methods are discovered at the run-time
Faster in execution as methods are discovered during compile-timeSlower in execution as methods are during the run-time

Thank you for taking your time and reading this article. If you do like Tootle Ten and are interested in contributing, you can write an article and mail it to tentootle[at]gmail.com. We will review the same and publish it with due credits.

Leave a Reply

Your email address will not be published. Required fields are marked *