Role Of Polymorphism In C++ With Examples.
Polymorphism is one of the four pillars of object-oriented programming. Polymorphism means having many forms. It can be defined as the technique by which an object can take many forms depending on the situation.
In programming terms, we can say that an object can behave differently in different conditions.
In this tutorial, we will learn about the types of polymorphism, the ways to implement polymorphism along with the various other concepts of polymorphism in detail.
=> Check Here To See A-Z Of C++ Training Tutorials Here.
For Example, a woman can take many roles in different situations. To a child, she is a mother, a homemaker at home, a worker in the office, etc. So a woman assumes different roles and exhibits different behavior in different conditions. This is a real-life example of polymorphism.
Similarly in programming world also, we can have an operator “+” that is the binary addition operator which behaves differently when the operands change. For Example, when both the operands are numeric, it performs addition.
On the other hand, when the operands are string, it acts as concatenation operator. Thus polymorphism, in a nutshell, means an entity taking up many forms or behaving differently under different conditions.
Table of Contents:
Types Of Polymorphism
Polymorphism is divided into two types.
- Compile time polymorphism
- Runtime polymorphism
The diagram to represent this is shown below:
As shown in the above diagram, polymorphism is divided into compile-time polymorphism and runtime polymorphism. Compile time polymorphism is further divided into operator overloading and function overloading. Runtime polymorphism is further implemented using virtual functions.
Compile time polymorphism is also known as early binding or static polymorphism. In this type of polymorphism, object’s method is invoked at the compile time. In the case of runtime polymorphism, the object’s method is invoked at run time.
Runtime polymorphism is also known as dynamic or late binding or dynamic polymorphism. We will look into the detailed implementation of each of these techniques in our following topics.
Compile Time Polymorphism Vs. Run-Time Polymorphism
Let us see the main differences between compile time and runtime polymorphism below.
Compile time polymorphism | Runtime polymorphism |
---|---|
Also known as static polymorphism or early binding | Also known as dynamic polymorphism or late/dynamic binding |
Objects method is invoked at compile time | Object’s method is invoked at run time |
Usually implemented using operator overloading and function overloading | Implemented using virtual functions and method overriding |
Method Overloading is a compile-time polymorphism in which more than one method can have the same name but different parameter list and types. | Method overriding is runtime polymorphism where more than one method has the same name with the same prototype |
Since methods are known at compile time, execution is faster | Execution is slower since the method is known at runtime |
Provide less flexibility to implement solutions since everything needs to be known at compile time | Far more flexible for implementing complex solutions as methods are decided at runtime |
Compile Time Polymorphism
Compile time polymorphism is a technique in which an object’s method is invoked at the compile time.
This type of Polymorphism is implemented in two ways.
- Function overloading
- Operator overloading
We will discuss each technique in detail.
Function Overloading
A function is said to be overloaded when we have more than one function with the same name but different parameter types or a different number of arguments.
Thus a function can be overloaded based on the parameter types, the order of parameters and the number of parameters.
Note that two functions having the same name and same parameter list but different return type is not an overloaded function and will result in a compilation error if used in the program.
Similarly, when function parameters differ only in pointer and if array type is equivalent, then it should not be used for overloading.
Other types like static and non-static, const and volatile, etc. Or parameter declarations that differ in presence or absence of default values are also not to be used for overloading as they are equivalent from the implementation point of view.
For Example, the following function prototypes are overloaded functions.
Add(int,int); Add(int,float); Add(float,int); Add(int,int,int);
In the above prototypes, we see that we overload the function Add based on the type of parameters, sequence or order of parameters, number of parameters, etc.
Let us take a complete programming Example to better understand Function Overloading.
#include <iostream> #include <string> using namespace std; class Summation { public: int Add(int num1,int num2) { return num1+num2; } int Add(int num1,int num2, int num3) { return num1+num2+num3; } string Add(string s1,string s2){ return s1+s2; } }; int main(void) { Summation obj; cout<<obj.Add(20, 15)<<endl; cout<<obj.Add(81, 100, 10)<<endl; cout<<obj.Add(10.78,9.56)<<endl; cout<<obj.Add("Hello ","World"); return 0; }
Output:
35
191
19
Hello World
In the above program, we have a Summation class that defined three overloaded functions named Add which takes two integer arguments, three integer arguments, and two string arguments.
In the main function, we make four function calls that provide various parameters. The first two function calls are straightforward. In the third function call to Add, we provide two floating point values as arguments.
In this case, the function that is matched is int Add (int, int) as internally, the float is converted to double and then matched with the function with the int parameters. If we had specified double instead of float, then we would have another overloaded function with double as parameters.
The last function call uses string values as parameters. In this case, the Add (+) operator acts as a concatenation operator and concatenates the two string values to produce a single string.
Benefits Of Function Overloading
The main benefit of function overloading is that it promotes code reusability. We can have as many functions as possible with the same name as long as they are overloaded based on the argument type, argument sequence and the number of arguments.
By doing this it becomes easier to have different functions with the same name to represent the behavior of the same operation in different conditions.
If function overloading was not present, then we would have had to write too many different kinds of functions with different names, thus rendering the code unreadable and difficult to adapt.
Operator Overloading
Operator overloading is the technique using which we give a different meaning to the existing operators in C++. In other words, we overload the operators to give a special meaning to the user-defined data types as objects.
Most of the operators in C++ are overloaded or given special meaning so that they can work on user-defined data types. Note that while overloading, the basic operation of operators is not altered. Overloading just gives the operator an additional meaning by keeping their basic semantic same.
Though most of the operators can be overloaded in C++, there are some operators which cannot be overloaded.
These operators are listed in the table below.
Operators |
---|
Scope resolution operator(::) |
Sizeof |
member selector(.) |
member pointer selector(*) |
ternary operator(?:) |
The functions that we use to overload operators are called “Operator functions”.
Operator functions are similar to the normal functions but with a difference. The difference is that the name of the operator functions begins with the keyword “operator” followed by the operator symbol that is to be overloaded.
The operator function is then called when the corresponding operator is used in the program. These operator functions can be the member functions or global methods or even a friend function.
The general syntax of the operator function is:
return_type classname::operator op(parameter list) { //function body }
Here “operator op” is the operator function where the operator is the keyword and op is the operator to be overloaded. Return_type is the value type to be returned.
Let us see few programming Examples to demonstrate the operator overloading using operator functions.
Example 1: Overloading of the unary operator using member operator function.
#include <iostream> using namespace std; class Distance { public: int feet; // Constructor to initialize the object's value Distance(int feet) { this->feet = feet; } //operator function to overload ++ operator to perform increment on Distance obj void operator++() { feet++; } void print(){ cout << "\nIncremented Feet value: " << feet; } }; int main() { Distance d1(9); // Use (++) unary operator ++d1; d1.print(); return 0; }
Output:
Incremented Feet value: 10
Here we have overloaded the unary increment operator using operator ++ function. In the main function, we use this ++ operator to increment the object of class Distance.
Example 2: Overloading of the binary operator using the member operator function.
#include<iostream> using namespace std; class Complex { int real, imag; public: Complex(int r = 0, int i =0) {real = r; imag = i;} //Operator function to overload binary + to add two complex numbers Complex operator + (Complex const &obj) { Complex c3; c3.real = real + obj.real; c3.imag = imag + obj.imag; return c3; } void print() { cout << real << " + i" << imag << endl; } }; int main() { Complex c1(2, 5), c2(3, 7); cout<<"c1 = "; c1.print(); cout<<"c2 = "; c2.print(); cout<<"c3 = c1+c2 = "; Complex c3 = c1 + c2; // calls overloaded + operator c3.print(); }
Output:
c1 = 2 + i5
c2 = 3 + i7
c3 = c1+c2 = 5 + i12
Here we have used the classic example of the addition of two complex numbers using the operator overloading. We define a class to represent Complex numbers and an operator function to overload + operator in which we add the real and imaginary parts of complex numbers.
In the main function, we declare two complex objects and add them using the overloaded + operator to get the desired result.
In the below example, we will use friend function to add two complex numbers to see the difference in implementation.
#include<iostream> using namespace std; class Complex { int real, imag; public: Complex(int r = 0, int i =0) {real = r; imag = i;} //friend function to overload binary + to add two complex numbers friend Complex operator +(Complex const &, Complex const &); void print() { cout << real << " + i" << imag << endl; } }; Complex operator + (Complex const &c1, Complex const &c2) { Complex c3; c3.real = c1.real + c2.real; c3.imag = c1.imag + c2.imag; return c3; } int main() { Complex c1(2, 5), c2(3, 7); cout<<"c1 = "; c1.print(); cout<<"c2 = "; c2.print(); cout<<"c3 = c1+c2 = "; Complex c3 = c1 + c2; // calls overloaded + operator c3.print(); }
Output:
c1 = 2 + i5
c2 = 3 + i7
c3 = c1+c2 = 5 + i12
We see the output of the program is the same. The only difference in the implementation is the use of friend function to overload the + operator instead of a member function in the previous implementation.
When friend function is used for a binary operator, we have to explicitly specify both the operands to the function. Similarly, when the unary operator is overloaded using friend function, we need to provide the single operand to the function.
Apart from the operator functions, we can also write a conversion operator that is used to convert from one type to another. These overloaded conversion operators should be a member function of the class.
Example 3: Operator overloading using conversion operator.
#include <iostream> using namespace std; class DecFraction { int numerator, denom; public: DecFraction(int num, int denm) { numerator = num; denom = denm; } // conversion operator: converts fraction to float value and returns it operator float() const { return float(numerator) / float(denom); } }; int main() { DecFraction df(3, 5); //object of class float res_val = df; //calls conversion operator cout << "The resultant value of given fraction (3,5)= "<<res_val; return 0; }
Output:
The resultant value of given fraction (3,5)= 0.6
In this program, we have used the conversion operator to convert the given fraction into a float value. Once the conversion is done, the conversion operator returns the resultant value to the caller.
In the main function, when we assign the df object to a res_val variable, the conversion takes place and the result is stored in res_val.
We can also call a constructor with a single argument. When we can call a constructor from the class using a single argument, this is called a “conversion constructor”. Conversion constructor can be used for implicit conversion to the class being constructed.
#include<iostream> using namespace std; class Point { private: int x,y; public: Point(int i=0,int j=0) {x = i;y=j;} void print() { cout<<" x = "<<x;cout<<" y = "<<y<<endl; } }; int main() { Point pt(20,30); cout<<"Point constructed using normal constructor"<<endl; pt.print(); cout<<"Point constructed using conversion constructor"<<endl; pt = 10; // here the conversion constructor is called pt.print(); return 0; }
Output:
Point constructed using normal constructor
x = 20 y = 30
Point constructed using conversion constructor
x = 10 y = 0
Here we have a class Point that defines a constructor with default values. In the main function, we construct an object pt with x and y coordinates. Next, we just assign pt a value of 10. This is where the conversion constructor is called and x is assigned a value of 10 while y is given the default value of 0.
Operator Overloading Rules
While performing operator overloading, we need to watch out the below rules.
- In C++, we are able to overload the existing operators only. Newly added operators cannot be overloaded.
- When operators are overloaded, we need to ensure that at least one of the operands is of the user-defined type.
- To overload certain operators, we can make use of friend function as well.
- When we overload unary operators using a member function, it doesn’t take any explicit arguments. It takes one explicit argument when the unary operator is overloaded using friend function.
- Similarly, when binary operators are overloaded using member function, we have to provide one explicit argument to the function. When binary operators are overloaded using friend function, the function takes two arguments.
- There are two operators in C++ that are already overloaded. These are “=” and “&”. Therefore to copy an object of the same class, we need not overload the = operator, and we can use it directly.
Advantages of Operator Overloading
Operator overloading in C++ allows us to extend the functionality of operators to the user-defined types including class objects in addition to the built-in types.
By extending the operator functionality to the user-defined types, we need not write complex code to perform various operations on user-defined types but, we can do it in one operation itself just like the built-in types.
Conclusion
Compile time polymorphism provides overloading facility mainly to extend the functionality of the code in terms of function overloading and operator overloading.
Through function overloading, we can write more than one function with the same name but different parameters and types. This makes the code simple and easily readable. By operator overloading, we can extend the functionality of operators, so that we can do basic operations on user-defined types as well.
In our upcoming tutorial, we will learn more about runtime polymorphism in C++.
=> Read Through The Easy C++ Training Series.