C++ Polymorphism

Understanding compile-time and runtime polymorphism

🔄 What is Polymorphism?

Polymorphism allows objects of different types to be treated as objects of a common base type. It enables one interface to represent different underlying forms.


// Simple polymorphism example
class Animal {
public:
    virtual void sound() { cout << "Some sound"; }
};

class Dog : public Animal {
public:
    void sound() override { cout << "Woof!"; }
};
                                    

Output:

Woof!

Types of Polymorphism

Compile-time

Function and operator overloading

int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
🏃

Runtime

Virtual functions and inheritance

virtual void display() = 0; // Pure virtual
virtual void show() { } // Virtual function
🎯

Method Overriding

Redefining base class methods

class Base { virtual void func(); };
class Derived { void func() override; };
🔗

Dynamic Binding

Method resolution at runtime

Base* ptr = new Derived();
ptr->func(); // Calls Derived::func()

🔹 Runtime Polymorphism Example

Runtime polymorphism in C++ is achieved through virtual functions and base class pointers or references. By declaring a function as virtual in a base class like Shape, derived classes such as Circle and Rectangle can override it. When you call draw() or area() on a Shape*, the correct derived class method executes based on the actual object type at runtime. This allows a single interface, like a collection of shapes, to manage diverse objects, calculating areas like 78.5397 for circles and 24 for rectangles dynamically.

class Shape {
public:
    virtual void draw() {
        cout << "Drawing a shape" << endl;
    }
    virtual double area() = 0; // Pure virtual
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    void draw() override {
        cout << "Drawing a circle" << endl;
    }
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    void draw() override {
        cout << "Drawing a rectangle" << endl;
    }
    
    double area() override {
        return width * height;
    }
};

int main() {
    Shape* shapes[] = {
        new Circle(5),
        new Rectangle(4, 6)
    };
    
    for(int i = 0; i < 2; i++) {
        shapes[i]->draw();
        cout << "Area: " << shapes[i]->area() << endl;
    }
    
    return 0;
}

Output:

Drawing a circle
Area: 78.5397
Drawing a rectangle
Area: 24

🔹 Compile-time Polymorphism

Compile-time polymorphism, also known as static polymorphism, is implemented via function overloading and templates. The compiler resolves which function to call based on the number and types of arguments at compile time. For example, you can have multiple add() functions handling integers, doubles, or custom types, returning results like 15 or 24. This technique improves performance by avoiding runtime dispatch and increases code clarity by providing intuitive, type-specific implementations. It is foundational for creating flexible APIs that work seamlessly with various data types.

class Calculator {
public:
    // Function overloading
    int multiply(int a, int b) {
        return a * b;
    }
    
    double multiply(double a, double b) {
        return a * b;
    }
    
    int multiply(int a, int b, int c) {
        return a * b * c;
    }
};

int main() {
    Calculator calc;
    
    cout << calc.multiply(5, 3) << endl;        // Calls int version
    cout << calc.multiply(2.5, 4.0) << endl;   // Calls double version
    cout << calc.multiply(2, 3, 4) << endl;    // Calls 3-parameter version
    
    return 0;
}

Output:

15
10
24

🧠 Test Your Knowledge

Which keyword is used for runtime polymorphism in C++?