Polymorphism in C++

2024-10-28 , 3 mins Read.

Polymorphism in C++ and Object-Oriented Design (OOD) refers to the ability of objects to behave differently based on their types. It allows one interface or function to be used for different types of objects, enabling flexibility and extensibility in code design. In simple terms, polymorphism allows different classes to respond to the same function call in different ways.

C++ supports two types of polymorphism: dynamic polymorphism and static polymorphism.

1. Dynamic Polymorphism (Run-time Polymorphism):

Dynamic polymorphism in C++ is achieved through inheritance and virtual functions, allowing the behavior of objects to be determined at runtime. This type of polymorphism allows a base class pointer or reference to point to objects of derived classes and invoke overridden methods of the derived class.

Key Concepts:

  • Virtual functions: A function in a base class that is declared with the keyword virtual and can be overridden by a derived class.

  • Overriding: A derived class can provide its own implementation for a virtual function from the base class.

  • Vtable (Virtual Table): C++ uses a virtual table (vtable) mechanism to implement dynamic polymorphism. Each class with virtual functions has a vtable, which is an array of function pointers. At runtime, when a virtual function is called via a base class pointer, the appropriate function from the vtable of the actual derived class is invoked.

Example:

#include <iostream>
using namespace std;

class Animal {
public:
    virtual void sound() {  // Virtual function
        cout << "Animal makes a sound" << endl;
    }
};

class Dog : public Animal {
public:
    void sound() override {  // Overriding the base class function
        cout << "Dog barks" << endl;
    }
};

class Cat : public Animal {
public:
    void sound() override {  // Overriding the base class function
        cout << "Cat meows" << endl;
    }
};

int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();

    animal1->sound();  // Outputs: Dog barks
    animal2->sound();  // Outputs: Cat meows

    delete animal1;
    delete animal2;
    return 0;
}

In this example, sound() is a virtual function, and its behavior is determined at runtime based on the actual object type (Dog or Cat) even though the calls are made via Animal*.

2. Static Polymorphism (Compile-time Polymorphism):

Static polymorphism is achieved at compile time using function overloading, operator overloading, or templates. The decision of which function or operator to invoke is made during compilation, hence it is faster than dynamic polymorphism since there is no runtime overhead.

Key Concepts:

  • Function Overloading: Functions with the same name but different parameter lists can coexist in the same scope, and the correct function is chosen based on the arguments provided at the call site.

  • Operator Overloading: C++ allows operators to be overloaded for user-defined types.

  • Templates: Templates allow writing generic code that works with different data types. Template instantiation happens at compile time, thus achieving polymorphism without the need for inheritance.

Example of Function Overloading:

#include <iostream>
using namespace std;

class Printer {
public:
    void print(int i) {
        cout << "Printing int: " << i << endl;
    }

    void print(double d) {
        cout << "Printing double: " << d << endl;
    }

    void print(string s) {
        cout << "Printing string: " << s << endl;
    }
};

int main() {
    Printer p;
    p.print(5);          // Outputs: Printing int: 5
    p.print(3.14);       // Outputs: Printing double: 3.14
    p.print("Hello");    // Outputs: Printing string: Hello
    return 0;
}

Example of Template-Based Static Polymorphism:

#include <iostream>
using namespace std;

template <typename T>
class Calculator {
public:
    T add(T a, T b) {
        return a + b;
    }
};

int main() {
    Calculator<int> intCalc;
    Calculator<double> doubleCalc;

    cout << "Int add: " << intCalc.add(3, 4) << endl;         // Outputs: Int add: 7
    cout << "Double add: " << doubleCalc.add(2.5, 3.5) << endl;  // Outputs: Double add: 6.0

    return 0;
}

Key Differences Between Dynamic and Static Polymorphism:

  • Dynamic Polymorphism:

    • Achieved using inheritance and virtual functions.

    • Resolved at runtime.

    • Uses a vtable for function dispatch.

    • Incurs some runtime overhead due to vtable lookup.

    • Supports only class hierarchies (inheritance-based).

  • Static Polymorphism:

    • Achieved through function overloading, operator overloading, and templates.

    • Resolved at compile time.

    • No runtime overhead.

    • Supports polymorphism across different types, not limited to class hierarchies.

Polymorphism in OOD:

In Object-Oriented Design (OOD), polymorphism is a key principle for designing flexible and extensible systems. It allows classes to be open for extension but closed for modification, following the Open/Closed Principle. In practical terms, this means that you can introduce new behaviors (through inheritance or templates) without changing existing code, promoting maintainability and scalability in large systems.

Last updated