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.





Advertisement