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.
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:
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 Polymorphism | Run-Time Polymorphism |
Also called early-binding or static binding | Also called lazy binding or dynamic binding |
The method is invoked at the compile time | The 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 time | More flexibility as methods are discovered at the run-time |
Faster in execution as methods are discovered during compile-time | Slower 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.
I am a Software Developer and Learning Enthusiast.
I love to code and create awesome technologies.
Currently I am developing MATLAB Unit Test Framework at MathWorks.
Here, I share my personal learnings and experiences that I think will benefit you 😉