C++ Polymorphism

Introduction

C++ being an object-oriented programming language gives us the features also known as paradigms of OOP like inheritance, encapsulation, data abstraction, and polymorphism.

Polymorphism being one of the most crucial paradigms helps in restructuring our code and also making it programmer-readable. Let’s dive deep into this.

What is polymorphism?

As the name suggests, poly means many and morphs means forms. Polymorphism works for functions.

Let’s understand it with an example, your favorite cartoon show, Ben 10. Ben was a simple human but had various alien forms which helped in defeating the enemies with their extraordinary powers and skillset. Very similar to this, while programming, we can’t continue to modify our function for every dataset but rather can create a set of functions with the same name to work with different datasets.

Polymorphism can be classified into two types:

  1. Compile TIme polymorphism
  2. Runtime polymorphism.

Compile-time polymorphism can be achieved by the following two methods: 

  1. Function/Method  overloading
  2. Operator overloading

Runtime polymorphism can be achieved by Function Overriding and virtual functions.

Now let’s try understanding these concepts and how they can be performed

Compile Time Polymorphism[Early Binding]

This type of polymorphism is also known as static polymorphism. This works when the program is getting converted to machine code i.e. during compilation. As discussed above, we can use function/method or operator overloading to perform this. Now we’ll try discussing how we can perform compile-time polymorphism

Function/Method overloading

When different functions with types or arguments have the same name, they are said to be overloaded

Let’s try understanding it with the help of code

Code:

C++ Code

#include<iostream>

using namespace std;

class TUF {
  public:
    int sum(int a, int b) { // function 1
      return a + b;
    }
  int sum(int a, int b, int c) { // function 2
    return a + b + c;
  }
  double sum(double a, double b) { // function 3
    return a + b;
  }
};
int main() {
  TUF obj;
  int val1 = obj.sum(1, 2, 3);
  int val2 = obj.sum(5, 6);
  double val3 = obj.sum(2.3, 5.3);
  cout << val1 << "\n";
  cout << val2 << "\n";
  cout << val3 << "\n";
  return 0;
}

Output:

6
11
7.6

Here we created 3 functions with the same name i.e. sum but of different return types and different arguments.

When we try creating an obj of the class and use the function name and try passing different arguments, the compiler itself identifies the appropriate function to be called which matches the type and the number of arguments. 

So when we call sum with two int arguments, function 1 is executed and for three int arguments, function 2  is executed and for double type arguments, function 3 is executed.

Operator Overloading

C++ has basic operators that perform various operations like increment, decrement, addition, subtraction, etc. We can overload this operator for performing other tasks also. This is known as operator overloading.

Let’s try understanding this with an example, we use the ‘+’ operator to add two numbers. 

int a = 3;
int b = 2;
int c = a + b; // 5

What if we want to add two complex numbers, some would also think about how we would create a complex number. We can make it by making a class for complex numbers and framing the real and the imaginary values. Let’s see by understanding the following code

class complex {
  int real;
  int imaginary;
  complex() {
    real = 0;
    imaginary = 0;
  }
  complex(int r, int i) {
    real = r;
    imaginary = i;
  }
  void print() {
    cout << real << " + " << imaginary << "i" << "\n";
  }
};

The above code would be the basic setup for our complex number formation.

Now let’s proceed to the implementation part. If we try to create two objects and simply use the ‘+’ operator.

int main() {
  complex c1(3, 2);
  complex c2(4, 3);
  complex c3 = c1 + c2;
}

This would throw an error as follows

For doing this, we need to overload the ‘+’ operator. The following code explains the overloading

Code:

C++ Code

#include<iostream>

using namespace std;

class complex {
  int real;
  int imaginary;
  public:
    complex() {
      real = 0;
      imaginary = 0;
    }
  complex(int r, int i) {
    real = r;
    imaginary = i;
  }
  complex operator + (complex c) {
    complex t;
    t.real = real + c.real;
    t.imaginary = imaginary + c.imaginary;
    return t;
  }
  void print() {
    cout << real << " + " << imaginary << "i" << "\n";
  }
};
int main() {
  complex c1(3, 2);
  complex c2(4, 3);
  complex c3 = c1 + c2;
  c3.print(); // 7+5i
}

Output: 7 + 5i

Runtime Polymorphism[Late Binding]

As the name suggests, the polymorphism executed during runtime is runtime polymorphism. This can be performed using function overriding in which virtual functions arrive. We shall discuss both of them briefly in this article.

Function Overriding

In inheritance, when a function of the same type has the same number of arguments but within two different classes i.e. parent class and child class, then the function is said to be overridden.

Invocation of those functions solely depends on the type of object created i.e. parent class or child class object.

Let’s check out the code for this

Code:

C++ Code

#include <iostream>

using namespace std;

class Parent {
  public:
    void print() {
      cout << "Parent Function" << endl;
    }
};

class Child: public Parent {
  public: void print() {
    cout << "Child Function" << endl;
  }
};

int main() {
  Parent p;
  Child c;
  p.print();
  c.print();

  return 0;
}

output:

Parent Function
Child Function

Virtual Functions

Looking at the function overriding, one can call it out as simple. But the problem arises when you start playing with pointers. According to the function overriding, function invocation totally depends on the object. But when we try creating a parent class pointer and pointing it to the child class object, then the parent class function is executed. Let’s check out the code for this.

Code:

C++ Code

#include <iostream>

using namespace std;

class Parent {
  public:
    void print() {
      cout << "Parent Function" << endl;
    }
};

class Child: public Parent {
  public: void print() {
    cout << "Child Function" << endl;
  }
};

int main() {
  Child childClassObject;
  Parent * parentClassPointer = & childClassObject;
  parentClassPointer -> print(); // Parent Function

  return 0;
}

Output: Parent Function

To prevent this, we can use virtual functions. Declare the parent class function as a virtual function by using the virtual keyword.

C++ Code

#include <iostream>
using namespace std;

class Parent {
  public:
    virtual void print() {
      cout << "Parent Function" << endl;
    }
};

class Child: public Parent {
  public: void print() {
    cout << "Child Function" << endl;
  }
};

int main() {
  Child childClassObject;
  Parent * parentClassPointer = & childClassObject;
  parentClassPointer -> print();

  return 0;
}

Output: Child Function

Special thanks to Yash Mishra for contributing to this article on takeUforward. If you also wish to share your knowledge with the takeUforward fam, please check out this article