C++ Polymorphisms
Introduction
Although there are many examples of C++ polymorphisms examples available on the Internet, it has been confusing to beginners that what exactly the polymorphism is, what kind of polymorphisms do we have in C++, and why do we really need polymorphisms.
In this blog post, I am going to elucidate these by walking through a couple of examples.
Definition
The generic definition of the word polymorphism is the occurrence of something in several different forms. In C++, it means that when calling a call to a function will cause a different function to be executed depending on the type of object that invokes the function.
More specifically, a function has a function name and inputs. When we call the functions using the same function name, supplying inputs of different types would cause different behaviors.
Categories
We have two categories of polymorphism in C++:
- Compile time polymorphism (static polymorphism, overloading, early binding)
- Runtime polymorphism (dynamic polymorphism, overriding, late binding)
The compile-time polymorphism could be further categorized as:
- Function overloading
- Operator overloading
- Template overloading
The runtime polymorphism is:
- Function overriding
I will walk through each of them and emphasize on the runtime polymorphism since it is usually the most confusing one.
Compile Time Polymorphism
Compile-time polymorphism, in my opinion, is relatively easy to understand. The reasons why we need this are obvious from the examples. Compile-time polymorphism, including function overloading and operator overloading, is not specific to object oriented programming. They are also used in generic functions and operators.
Examples
Function overloading and operator overloading
1 |
|
Template overloading
1 |
|
Function Overloading
From the above example, we could see that in order to execute add
for different types of data, we have implemented Calculator::add
method for different input data types. At compile time, given the input types, the compiler would link the method to the corresponding methods.
Operator Overloading
Operator overloading is similar to function overloading. Function overloading is targeting function polymorphism whereas operator overloading is targeting operator polymorphism.
Template Overloading
Template overloading is simply providing an convenient way to partially save the work that we manually have to do for function overloading.
Runtime Polymorphism
As mentioned before, runtime polymorphism is just function overriding. It is achieved via virtual
methods.
Examples
There are a couple of examples on the Internet which I think is misleading. One of them is like this:
1 |
|
The output of the program is:
1 | The area for rectangle: |
This tells us that a base pointer could dynamically point to the overridden virtual functions of the corresponding inherited classes. With each same call shape->getArea()
, it executes different functions when shape
is pointing to different inherited classes.
This polymorphism looks OK, but unlike the compile-time polymorphism, it is not obvious from this example why we really need this inheritance-based polymorphism is achieved via virtual
methods. The following example, which has not used virtual
methods and has almost the same amount of code as the previous one, could do the exact the same thing. Why would we need this complicated virtual
methods?
1 |
|
I modified the example a little bit and we would see the advantages of using virtual
methods for polymorphism.
1 |
|
Function Overriding
Here I used computeArea
to compute the area of a given shape, and we pretend this computeArea
function is very long and the variable shape
would be used a lot of times in the function. By just implementing the compute computeArea
once, we would be able to compute the area for any Shape
whose getArea
method has been implemented. We don’t have to use function overloading to implement the (potentially very long) computeArea
for Triangle
, Rectangle
, or any other new shapes.
Compile Time Polymorphism VS Runtime Polymorphism
Virtual VS Template
OK, someone may still argue that using compile-time polymorphism template
, we can do the same thing without implementing computeArea
for different Shapes
using the following implementation.
1 |
|
So it looks like we could do the exact same thing using either virtual
methods or template
. Using template
has better performance since it is compile-time polymorphism. However, which one to use in practice is a little bit ambiguous to me. Probably using virtual
methods is more “object-oriented” and it does save the memory consumption of the program if there are many classes inheritances.
Miscellaneous
VTable
Here is an interesting question. How does the base pointer know which is the correct virtual method to call?
If a class contains a virtual function then compiler itself does two things:
- Irrespective of an instance of the class is created or not, a static array of function pointer called VTable, where each cell contains the address of each virtual function contained in that class, will be created.
- Each of the instances of the class created will have a virtual pointer (VPtr) as a member variable pointing to the VTable of the class.
So when we have a base pointer pointing to the instance of the inherited class, we could get access to the VPtr of the instance. Depending on the virtual method we called, the corresponding function on the VTable would be found via VPtr and executed.
Related Readings
- Polymorphism in C++
- Polymorphism and Template-Based Designs
- Virtual Function in C++
- Bjarne Stroustrup. Programming: Principles and Practice Using C++. Second Edition. P506-507.
C++ Polymorphisms