Polymorphism
Polymorphism is a mechanism by which you provide single interface for multiple methods. (poly - many, morph - form)
In C++, compile time polymorphism is provided with the help of overloaded operators/functions and templates. Run time polymorphism is provided with the help of virtual functions and late binding.
Late Binding:
Connecting a function call to function body is called binding. Most functions use early binding where this binding happens before the program is run - it happens during compile time.
Late binding is when a function call is connected to function body at run time after looking at the type of the object. Late binding is achieved in C++ using the key word "virtual" with function declaration.
Virtual functions:
To use polymorphism, you need to declare any one base class function as virtual function. Once it is declared as virtual function and if a derived class object is accessed using base class pointer or reference, the virtual function call will invoke most derived class function instead of the base class function.
A function is made virtual by using keyword virtual in its declaration.
Let us look at an example.
In the code above, Shape class has draw() function as virtual. And the two derived classes Rectangle and Circle are overriding this virtual function.
Now in main() function, a pointer to shape class is defined and it is assigned to dynamically created Circle object. When ptr->draw() is called, instead of invoking shape.draw(), the program invokes circle.draw() because at run time, ptr points to a circle object.
Similarly when ptr points to Rectangle object, ptr->draw() calls draw() function of Rectangle class, even though, ptr is a pointer to shape class.
Output
Drawing a rectangle Drawing a circle Drawing a shape
ptr->draw() which is a virtual function, calls different functions- first it calls Rectanlge class draw() function when ptr is pointing to a rectangle. Next it calls Circle class draw()function when ptr is pointing to a circle etc.
As the call binding happens at run time, the system finds out the type of object(class) ptr is pointing to and calls draw() function of that class. So ptr behaves like a circle, a rectangle and a shape. This is polymorphism.
Output
Num of sides is 3 Num of sides is 4 Num of sides is unknown
In the program above, printPolygon() accepts a polygon reference as parameter. And as derived class object ISA a base class object, the parameter to printPolygon() function can be either a polygon object or a triangle or a rectangle. In the program, this function is called with a triangle, a rectangle and a polygon.
printPolygon() calls printNS() function. When argument to printPolygon() is t - a triangle, printNS() of triangle class is called by the function. When it is a rectangle, rectangle printNS() is called.
Exercise: What happens when you remove virtual keyword from printNS() function in base class?
Do you notice the difference between the previous program and this? Previous program used pointers and this one used reference. Polymorphism is exhibited only if we use reference or pointer, not if we use derived class objects directly.
Virtual Table and vptr
Every class in C++ with at least one virtual function, will have a table called VTABLE which will have names and addresses of every virtual function of the class.
Object of every class which has a virtual function will have a hidden data member called vptr. When an object is created, vptr will initialized with address of VTABLE of its class.]]
e.g. If the object belongs to Circle class, the vptr points to VTABLE of Circle class.
When a virtual function is called, vptr is used to get address of VTABLE. Using VTABLE address of virtual function is obtained. And this function is invoked.
Let us add few functions to the first program with shape, rectangle and circle used earlier in sub-section 1.
Output
Drawing a circle
Drawing a shape
For the printShape() function, the type of parameter is Shape. So the function binding happens at compile time and there will not be polymorphism.
But for printShapeRef() function, the parameter could be a shape or any of its derived class object (because a Circle is a Shape, Rectangle is also a Shape). So, the function binding is deferred until runtime. During code execution, the s.draw() gets vptr of argument s. Using vptr, VTABLE is obtained and using VTABLE address of draw() function is obtained.
So when printShapeRef is called with a Circle object, vptr points to VTABLE of Circle class. From this table, draw() function of Circle class is obtained, draw() of circle is called. But when argument to printShateRef() is a Shape object, vptr points to VTABLE of Shape class. So draw() of Shape class is called.
Exercise:
Find the size of object which has at least one virtual function. What do you observe?
Note:
-
Virtual function can not be static
-
Constructor can not be virtual but destructor can be virtual.
-
Virtual function overridden in derived class, must have the exact same signature as base class function.
-
Polymorphism is not exhibited when there is direct object. It is exhibited only with pointers and references.
Virtual Constructor and Destructor
A class CAN NOT have a virtual constructor. But it can have a virtual destructor.
In fact a virtual destructor is desirable in certain cases. Let us look at an example which shows us the need for virtual destructor.
We are defining a pointer to base class and assigning it to an object of derived class B.
What do you expect the output to be? You expect output like this?
A constructorB constructorB destructorA destructor
Sorry. That does not happen. Since ptr is A class pointer, delete ptr will only call A destructor. So the derived class (B) destructor is not called at all.
Actual Output:
A constructorB constructorA destructor
But if we make base class destructor as virtual, when a pointer to base class which is pointing to derived class object is being destroyed, the run time system will call derived class destructor because of polymorphism. But derived class destructor always calls base class destructor. So both base class as well as derived class destructors will get called.
Let us make the destructor of A class as virtual in the previous example.
Now when we use delete ptr, destructor of A will be called. But because A destructor is virtual and ptr points to B class object, there will be late binding and B destructor will be called. But a derived class destructor always calls base class destructor after its execution. So even A destructor will be called.
The output will be:
A constructorB constructorB destructorA destructor