Polymorphism in C++

In this blog post tutorial, you will learn about polymorphism in C++ with the help of programming examples. I will describe each small point related to the polymorphism and I will try to make you zero to hero. This blog post on polymorphism will be helpful for both beginners and experienced. So let’s start this blog post with a question “what is polymorphism in C++”?

 

What is polymorphism in C++?

Polymorphism is one of the four basic concepts of OOP (Object Oriented Programming) that are Inheritance, Abstraction, Polymorphism, and Encapsulation. It is often referred to as the third pillar of object-oriented programming, after encapsulation and inheritance.

The word polymorphism is a Greek word that means “many-form“. So polymorphism in C++ means, the same entity (method or object) behaves differently in different scenarios. Let’s consider a real-life example of polymorphism. A man behaves like an employee in the office, a father, husband, or son in a home, and a customer in a market. So the same man possesses different behavior in different situations. This is called polymorphism.

If we talked about polymorphism in reference to C++ programming then you observe the + operator in C++ it is used to perform two specific functions in two different scenarios. When it is used with numbers (like integers or floating-point numbers), it performs addition.

int val1 = 27;

int val2 = 6;

int sum = val1 + val2;  // sum =33

 

And when we use the + operator with strings, it performs string concatenation. See the below example,

string str1 = "aticle";


string str2 = "world";


string str = str1 + str2; //aticleworld

 

I hope now you are able to understand polymorphism. In the below section you will learn how to implement the concept of polymorphism in C++. But before understanding that how we can implement polymorphism in C++ I want to categories the polymorphism, so let us see the type of polymorphism in C++.

Types of Polymorphism in C++

We can categorize polymorphism into two types. These are Compile-time polymorphism and Run-time polymorphism. See the below image.

Polymorphism in c++

 

Compile-time polymorphism:

The Compile-time polymorphism is a polymorphism that happens at compile time. What this means is that the compiler must know what is going on. This is also mentioned as static time polymorphism, compile-time binding, static binding, early binding.

We can implement the compile-time polymorphism in C++ using the function-overloading, operator overloading, and using the templates. I will explain each one step by step.

 

C++ Function overloading:

C++ allows more than one function or function template with the same name in the same scope. These functions are called overloaded functions. Overloaded functions enable you to supply different semantics for a function, depending on the types and number of arguments.

See the below example in which I have created the three “sum” functions in the same scope. Each sum function is taking different types of arguments. Like the first “sum” function is taking two integers second sum function is taking two floats and the third sum function is taking three third integers. Overloading saves you from having to use different names, using a single name you can perform different tasks.

// sum with 2 int parameters
int sum(int num1, int num2)
{
    return (num1 + num2);
}



// sum with 2 float parameters
float sum(float num1, float num2)
{
    return (num1 + num2);
}



// sum with 3 int parameters
int sum(int num1, int num2, int num3)
{
    return (num1 + num2 + num3);
}

 

At compile time, the compiler chooses which overload to use based on the type of arguments passed in by the caller. It means the compiler knows which function to execute before the program is compiled.  It is the reason we call it compile-time polymorphism.

If you call sum(6, 27), then the int sum(int num1, int num2) function will be invoked. If you call sum(6.25, 27.0), then the float sum(float num1, float num2) overload will be invoked. Also similarly If you call sum(6, 25, 27), then the int sum(int num1, int num2, int num3) function will be invoked. Let’s see the complete program to understand the above explanation.

#include <iostream>
using namespace std;

// sum with 2 int parameters
int sum(int num1, int num2)
{
    return (num1 + num2);
}

// sum with 2 float parameters
float sum(float num1, float num2)
{
    return (num1 + num2);
}

// sum with 3 int parameters
int sum(int num1, int num2, int num3)
{
    return (num1 + num2 + num3);
}

int main()
{
    // Call sum function with 2 int parameters
    cout << "Calling Sum with 2 int = " << sum(6, 27) << endl;

    // Call sum function with 2 double parameters
    cout << "Calling Sum with 2 float = " << sum(6.25f, 27.0f) << endl;

    // Call sum function with 3 int parameters
    cout << "Calling Sum with 3 int " << sum(6, 25, 27) << endl;

    return 0;
}

Output:

Polymorphism with overloaded function

 

 

C++ Operator Overloading:

In C++, operator overloading allows you to redefine the functionality of the allowed operators, such as “+”, “-“, “=”, “>>”, “<<".  The operator keyword is used for operator overloading in C++. The compiler distinguishes between the different meanings of an operator by examining the types of its operands.

Almost any operator can be overloaded in C++. However, there are few operators that can not be overloaded in C++.  I have mentioned the few operators who can not be overloaded in C++.

Operator Name
. Member selection
.* Pointer-to-member selection
:: Scope resolution
? : Conditional
# Preprocessor convert to string
## Preprocessor concatenate

 

Note: The sizeof operator can also not be overloaded.

 

See the below example, I am overloading the + operator to add two objects of the Test class and return the result and print the same. This is also a compile-time polymorphism because the compiler knows which operator needs to call overloaded or in-built.  You can learn more about Operator Overloading, visit our C++ Operator Overloading tutorial and Faq.

#include <iostream>
using namespace std;


//class Test
class Test
{
public:
    //constructor
    Test( int data1, int data2 ) : m_data1(data1), m_data2(data2) {}
    Test operator+( Test &rObj);
    //print the value
    void print( )
    {
        cout << "m_data1 = " << m_data1 <<endl;
        cout << "m_data2 = " << m_data2 << endl;
    }
private:
    //member variables
    int m_data1,m_data2;
};


// Operator overloaded using a member function
Test Test::operator+( Test &rObj )
{
    return Test( m_data1 + rObj.m_data1, m_data2 + rObj.m_data2 );
}


int main()
{
    Test obj1(1,2);
    Test obj2(5,6);
    Test obj3(0,0);


    //adding two object of class Test
    obj3 = obj1 + obj2;

    //print the result of addition
    obj3.print();

    return 0;
}

Output:

Operator overloading in c++

 

 

Compile-time polymorphism using a template:

We can achieve compile-time polymorphism by templates in C++. We can achieve it by Function templates or Class Templates, it is totally up to your requirement which one you should use in your code. I have already written a blog post on it if you want you can check it, Compile Time Polymorphism with Templates in C++.

In the below example, I am going to create a function templatecustom_add()that can add any built-in data type. The compiler’s responsibility is to generate code for different input types based on the instructions you gave. See the below-mentioned code.

#include <iostream>

template <class T>
void custom_add (T val1, T val2)
{
    std::cout << "Addition = " << (val1 + val2) << std::endl;
}

int main ()
{
    custom_add<int>(3, 5);    // type specifier <int> present

    custom_add<float>(3.2, 4.5); // type specifier <float> present

    custom_add<double>(3.2123, 4.5456); // type specifier <float> present

    return 0;
}

Output:

Addition = 8
Addition = 7.7
Addition = 7.7579

 

 

Run-time polymorphism:

Run time polymorphism is achieved when the object’s method/function is called/invoked at the run time instead of compile time. It is achieved by method overriding which is also known as dynamic binding or late binding.

Run time polymorphism means the compiler must generate code for all the types the program might handle while running, and at run-time, the correct code is selected with the help of VTABLE. The virtual keyword play important role in the implementation of run-time polymorphism in C++.

 

C++ Function Overriding:

Function overriding, in object-oriented programming, is a language feature that allows a subclass or child class to provide a specific implementation of a function that is already provided by one of its superclasses or parent classes.

So when a child class defines a function that is already defined in the parent class, it is called function overriding in C++. When we call the function using an object of the child class, the function of the child class is executed instead of the one in the parent class.

Function overriding helps us achieve runtime polymorphism because different functions are executed depending on the object calling the function.

Consider the below example for a better understanding. Here, we have used a display() function in the Parent class and the same function in the Child class.

When we call display() using the Parent object “A”, the display() function of the Parent class is called. But when we call display() using the Derived object “B”, it overrides the display() function of Parent by executing the display() function of the Child class.

#include <iostream>
using namespace std;

class Parent
{
public:
    void display()
    {
        cout<<"I am parent class..."<<endl;
    }
};


class Child: public Parent
{
public:
    void display()
    {
        cout<<"I am child class..."<<endl;
    }
};

int main()
{
    Parent A =Parent();
    A.display();   //parent class object

    Child B = Child();
    B.display();   // child class object

    return 0;
}

Output:

I am parent class…
I am child class…

 

C++ Virtual Functions:

When derived class overrides the base class function by redefining the same function; And If a client wants to access redefined function of a derived class through a pointer of the base class object, then you must define this function in the base class as a virtual function.

In C++, you are not able to override functions if we use a pointer of the base class to point to an object of the derived class.

The virtual functions are implemented using a table of function pointers, called the VTABLE. There is one entry in the table per virtual function in the class. This table stores the address of the virtual function and it is created by the constructor of the class.

The object of the class contains the virtual pointer (vptr) that points to the base address of the virtual table in memory. Whenever there is a virtual function call, the v-table is used to resolve the function address. It is a runtime polymorphism because the function call is not resolved at the compiled time by the compiler, but it is resolved at the runtime.

Let see an example, where the derived class function is called by base class pointer using virtual keyword.

#include<iostream>
using namespace std;

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


class derived: public base
{
public:
    void print()
    {
        cout << "print derived class" << endl;
    }
};

int main()
{
    //derive class object
    derived d;
    
    //Base class pointer
    base *b = &d;
    
    // virtual function, binded at runtime
    b->print();
    
    return 0;
}

Output:

print derived class

 

Difference between compile-time polymorphism and runtime polymorphism:

The following table describes the basic difference between compile-time polymorphism and run-time polymorphism.

Compile-time polymorphism Run time polymorphism
The function called resolved at the compile time. The function called resolved at the run time.
It is also known as overloading, early binding, and static binding. It is also known as overriding, Dynamic binding, and late binding.
Inheritance is not required for compile-time polymorphism. Inheritance is required for compile-time polymorphism.
It provides fast execution as it is known at the compile time. It provides slow execution as it is known at the run time.
The virtual keyword is not involved here. The virtual keyword plays an important role here.
It is less flexible as mainly all the things execute at the compile time. It is more flexible as all the things execute at the run time.

 

 

Why use polymorphism in C++?

There are many reasons to use polymorphism in the code but the most important reason is its decoupling nature. Polymorphism is used to break the dependencies and make the code cleaner and more loosely coupled.

I believe you know the tight and loose coupling. Basically, tight coupling happens when a group of classes is highly dependent on one another. It is a very difficult and a nightmare to test the tightly coupled class.

A tightly coupled scenario arises when a class assumes too many responsibilities, or when one concern is spread over many classes rather than having its own class. For example,

#include <iostream>
using namespace std;

class Rectangle
{
private:
    int width, height;
public:
    Rectangle(int a = 0, int b = 0):width(a),height(b)
    {

    }
    int area ()
    {
        return (width * height);
    }
};



class Shape
{
private:
    class Rectangle &m_rRectangle;

public:
    Shape(Rectangle &rRectangle):m_rRectangle(rRectangle)
    {
    }
    void area()
    {
        cout << "Area :" << m_rRectangle.area() <<endl;
    }
};

// Main function for the program
int main()
{
    class Rectangle obj1(4,5);
    class Shape obj2(obj1);

    obj2.area();

    return 0;
}

Output:

Area: 20

In the above example, you can see Shape and Rectangle class is tightly coupled to each other. The Shape class is not fulfilling the task for which we have created it. It can only calculate the area of the reactangle. But if you want to calculate the area of the triangle you have to modify the class.  Also if you want to calculate the area of the pyramid, again you need to modify it.  Every time you also need to test the Shape class combined with other concrete classes. Also, sometimes constructor changes break the backward compatibility.

We can solve this problem using polymorphism. We can make the classes loosely coupled to each other. Loose coupling is achieved by means of a design that promotes single-responsibility and separation of concerns. A loosely-coupled class can be consumed and tested independently of other (concrete) classes.

Here interface plays an important role and it helps to decouple the classes. Classes can communicate through interfaces rather than other concrete classes. Let’s see an example, it comes under the dynamic polymorphism.

#include <iostream>
using namespace std;


class IShape
{
public:
    virtual ~IShape() {}
    virtual int area() = 0;
};


class Rectangle :public IShape
{
private:
    int width, height;
public:
    Rectangle(int a = 0, int b = 0):width(a),height(b)
    {

    }
    int area ()
    {
        return (width * height);
    }
};


class Triangle :public IShape
{
private:
    int width, height;
public:
    Triangle(int a = 0, int b = 0):width(a),height(b)
    {

    }
    int area ()
    {
        return (width * height / 2);
    }
};


class Shape
{
private:
    class IShape &m_rShape;

public:
    Shape(IShape &rShape):m_rShape(rShape)
    {
    }
    void area()
    {
        cout << "Area :" << m_rShape.area() <<endl;
    }
};

 

 

Recommended Articles for you:

Leave a Reply

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