Polymorphism is a compelling concept that allows the design of an amazingly flexible application. The word Polymorphism is derived from two Greek words Poly which means many, and morphos which means forms. So, Polymorphism means the ability to take many forms.
Polymorphism in C++ can be defined as one interface multiple methods, which means that one interface can perform various but related activities. The concept of function overloading and operator overloading that we have already discussed implements Polymorphism as they provide a single interface for performing various but related tasks. In the case of function overloading, the single interface is the name of the function. In operator overloading, the single interface is the operator symbol. Let us consider an example of calculating the sum of two integers, two floating-point numbers, and two strings (concatenation). Using function overloading, we can overload the same-named sum() function for performing all these operations instead of using separate named functions for each of the operations. Thus it shows that a single interface (i.e. name of the function, sum()) can perform multiple tasks. Similarly, operator overloading, the+ operator (single interface), can be extended to concatenate two string objects. So Polymorphism is the ability to call various functions by just using one type of function call.
We can also take advantage of inheritance for implementing Polymorphism. The Polymorphism due to inheritance is often considered as the greatest contribution to object-oriented language. To understand this concept, consider an example of a collection of shapes in a graphic package. Each shape in it may be completely different as each may be of different structure and have a different way of drawing such as rectangle, circle, triangle, etc. If we want to draw different shapes, the same thing has to be performed in principle (i.e., draw a shape) but depending on the shape, it may look very different as the method for drawing each shape is different. The user’s request in everyday language to draw a shape will therefore lead to various actions. A separate version of drawing a shape needs to be implemented for every class that describes a kind of shape (derive class) to simulate this. The class hierarchy is as shown in the following figure.
The draw() function is present in the base class SHAPE to draw any shape in the above figure. As the technique for drawing different shapes is unique, so we have redefined the draw() function in each of the derived classes. With polymorphism, whenever a user wants to draw a particular shape (say circle), it merely sends the request to the base class SHAPE by invoking its function draw(), which in reality be calling a function draw() of the appropriate derived class to display the requested shape.
The advantage of polymorphism is that even for such a heterogeneous collection of different shapes, the general draw() function can be invoked, automatically resulting in the respective function being called according to the actual type of shape. In other words, the user need not be concerned about which draw() function is executed to fulfill its requests. In the above example, the single interface is the draw() function present in the base class SHAPE, and various tasks are the draw() functions present in the derived classes.
So polymorphism can also be defined as the ability of objects of different classes (derived classes) to respond to the same request, each in its way.
The main benefit of polymorphism is that it reduces complexity by allowing the same name to perform various tasks. As in the above example, the same-named function draw() can be used for drawing any shape.
Another benefit of polymorphism is that it is possible to design and implement easily extensible systems. New classes can be added to the class hierarchy with little or no modification in the base class. For example, if the user wishes to draw another shape ellipse, we need to add ellipse class m the class hierarchy, and there is no need to modify any code in the base class SHAPE.
In C++, to implement polymorphism with inheritance hierarchies, virtual functions are used.
Classification of Polymorphism
We can classify polymorphism in two ways on the basis of binding performed by the compiler for all the related but different operations having a common name (i.e. polymorphic methods). The concept of binding refers to the linking of function call to the code of the function to be executed in response to the function call.
The two types of polymorphism are :
Compile time (or Static) polymorphism
In Compile time polymorphism, static binding is performed. In static binding, the compiler decides the selection of appropriate function to be called in response to the compile time call. It is because all the address information requires to call a function is known at compile time. It is also known as early binding, as the compiler decides binding at the earliest possible moment. The advantage of static binding is its efficiency, as it often requires less memory, and function calls are faster. Its disadvantage is the lack of flexibility. The compile-time polymorphism is implemented in C++ using function overloading and operator overloading. As in both cases, the compiler has all the information about the data type and some arguments needed to select the appropriate function at compile time.
Runtime (or Dynamic) polymorphism
In Run time polymorphism, dynamic binding is performed. In dynamic binding, the decision regarding selecting the appropriate function to be called is made by the compiler at run time and not at compile time. The selection of appropriate function definition corresponding to a function call is known only at run time. It is also known as late binding, as the compiler delays the binding decision until run time.
The advantage of dynamic binding is that it allows greater flexibility by enabling the user to create class libraries that can be reused and extended as per requirements. Moreover, it provides a common interface in the base class for performing multiple tasks whose implementation is present in the derived classes. The main disadvantage of dynamic binding is that there is little loss of execution speed. The compiler will have to perform certain overheads at run time (inserting extra code to perform late binding).
C++ is a hybrid language as it supports both static and dynamic binding, whereas specific object-oriented languages like Smalltalk allow only run-time binding of the function. One should note that polymorphism, if not explicitly specified, is run-time polymorphism.
In C++, run time polymorphism is implemented using virtual functions.