Runtime Polymorphism: Virtual Functions and Function Overriding in C++
Runtime polymorphism is a powerful feature in C++ that allows a function call to be resolved at runtime based on the type of object that invokes it, rather than the type of pointer or reference used for the function call. It is typically achieved using virtual functions and function overriding in C++.
Virtual Functions
A virtual function in C++ is a member function in the base class that you expect to be overridden in derived classes. When a function is declared as virtual, C++ uses dynamic dispatch to resolve which version of the function to call at runtime based on the actual type of the object.
The virtual function mechanism is a key feature of runtime polymorphism because it allows you to call the appropriate method for an object even when you use a base class pointer or reference to point to derived class objects.
Example of Virtual Function
#include <iostream> class Animal { public: virtual void sound() { std::cout << "Animal makes a sound" << std::endl; } }; class Dog : public Animal { public: void sound() override { std::cout << "Dog barks" << std::endl; } }; class Cat : public Animal { public: void sound() override { std::cout << "Cat meows" << std::endl; } }; int main() { Animal* animal1 = new Dog(); Animal* animal2 = new Cat(); animal1->sound(); // Calls Dog's sound() method animal2->sound(); // Calls Cat's sound() method delete animal1; delete animal2; return 0; }
Output:
Dog barks Cat meows
Explanation
In this example, the base class Animal
has a virtual function sound()
. Both the Dog
and Cat
classes override this function to provide their own implementation. In the main
function, although we use Animal*
pointers to refer to objects of type Dog
and Cat
, the actual method called at runtime is determined by the type of the object, not the type of the pointer. This is an example of runtime polymorphism in C++.
Function Overriding
Function overriding occurs when a derived class provides its own implementation of a function that is already defined in its base class. The base class function should be virtual, and the derived class function should use the override
keyword (optional but recommended) to ensure proper overriding.
Function overriding enables the derived class to tailor the behavior of a function that was inherited from the base class.
Example of Function Overriding
#include <iostream> class Shape { public: virtual void draw() { std::cout << "Drawing a generic shape" << std::endl; } }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle" << std::endl; } }; class Rectangle : public Shape { public: void draw() override { std::cout << "Drawing a rectangle" << std::endl; } }; int main() { Shape* shape1 = new Circle(); Shape* shape2 = new Rectangle(); shape1->draw(); // Calls Circle's draw() method shape2->draw(); // Calls Rectangle's draw() method delete shape1; delete shape2; return 0; }
Output:
Drawing a circle Drawing a rectangle
Explanation
Here, the draw()
function is declared as virtual in the Shape
base class. Both the Circle
and Rectangle
classes override this function to provide their specific drawing behavior. In the main
function, even though we use a base class pointer to refer to the derived class objects, the correct draw()
function is called at runtime based on the actual type of the object.
Why Use Virtual Functions and Function Overriding?
Virtual functions and function overriding provide the foundation for runtime polymorphism in C++. They are essential when you need to design flexible and extensible systems. For instance, when you want to treat a group of objects with a common interface (i.e., the base class), but you also want each derived class to have its own behavior for certain methods. This technique allows you to implement generic code that works with any subclass of a base class, yet still gives each subclass the ability to tailor certain behaviors.
Virtual Destructor
In addition to virtual functions, it is also important to make destructors virtual in a base class when dealing with inheritance. If a derived class is deleted through a base class pointer, the base class destructor should be virtual to ensure that the derived class destructor is called, thus preventing resource leaks.
Example of Virtual Destructor
#include <iostream> class Animal { public: virtual ~Animal() { std::cout << "Animal Destructor Called" << std::endl; } }; class Dog : public Animal { public: ~Dog() override { std::cout << "Dog Destructor Called" << std::endl; } }; int main() { Animal* animal = new Dog(); delete animal; // Calls Dog's destructor followed by Animal's destructor return 0; }
Output:
Dog Destructor Called Animal Destructor Called
Explanation
In this example, the destructor of the Animal
class is declared as virtual. When we delete the animal
pointer (which points to a Dog
object), the destructor of the Dog
class is called first, followed by the destructor of the Animal
class. This ensures that the resources of both the base and derived classes are properly cleaned up.
Conclusion
Runtime polymorphism in C++ is a powerful mechanism that provides flexibility and extensibility in object-oriented systems. By using virtual functions and function overriding, you can define base class methods that can be customized by derived classes, and the correct function is called at runtime. This makes your code more modular and easier to extend without modifying existing code, a key principle of good object-oriented design.