Site icon Afzal Badshah, PhD

Polymorphism in C++ | OOP Tutorial with Real-Life Examples

Polymorphism is one of the four core concepts of Object-Oriented Programming (OOP), along with encapsulation, inheritance, and abstraction. The term polymorphism is derived from two Greek words; poly (many) and morph (forms). In programming, it means one name, many forms. In simple words, polymorphism allows a single function, operator, or object to behave differently based on the context. This makes our code flexible, reusable, and easy to extend.

Understanding Polymorphism

Think about the word “drive”.
A car drives, a bike drives, and a bus drives. The action “drive” is the same, but the behavior is different depending on the object. This is polymorphism; the same operation acting differently for different entities.

Similarly, in a company, the manager, designer, and developer all respond to the command “Work!”, but each performs a different task. This is how polymorphism works in real life and also in programming.

What Is Polymorphism in C++

Polymorphism in C++ is the ability of a function, operator, or object to take many forms. It allows the same function name to perform different tasks depending on the type of object or the number of parameters. Polymorphism provides the foundation for dynamic behavior in software, allowing us to write programs that can adapt at runtime.

Types of Polymorphism in C++

C++ supports two main types of polymorphism:

TypeDescriptionExample
Compile-Time Polymorphism (Static)Decided by the compiler during compilation.Function Overloading, Operator Overloading
Run-Time Polymorphism (Dynamic)Decided during program execution.Virtual Functions / Function Overriding

Compile-Time (Static) Polymorphism

Compile-time polymorphism means the decision about which function to call is made before the program runs. This is achieved using function overloading and operator overloading.

Function Overloading Example

#include <iostream>
using namespace std;

class Math {
public:
    int add(int a, int b) {
        return a + b;
    }

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

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

int main() {
    Math m;
    cout << "Sum of two integers: " << m.add(2, 3) << endl;
    cout << "Sum of two doubles: " << m.add(2.5, 3.8) << endl;
    cout << "Sum of three integers: " << m.add(1, 2, 3) << endl;
    return 0;
}

Operator Overloading Example

#include <iostream>
using namespace std;

class Complex {
    int real, imag;
public:
    Complex(int r = 0, int i = 0) {
        real = r;
        imag = i;
    }

    Complex operator + (Complex obj) {
        Complex temp;
        temp.real = real + obj.real;
        temp.imag = imag + obj.imag;
        return temp;
    }

    void display() {
        cout << real << " + " << imag << "i" << endl;
    }
};

int main() {
    Complex c1(3, 2), c2(1, 7);
    Complex c3 = c1 + c2;  // '+' is overloaded
    c3.display();
    return 0;
}

Run-Time (Dynamic) Polymorphism

Run-time polymorphism means the function that gets executed is determined at runtime, not compile time. This is achieved through function overriding using virtual functions and pointers/references to base classes.

Function Overriding Example

#include <iostream>
using namespace std;

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

class Dog : public Animal {
public:
    void sound() {
        cout << "Dog barks" << endl;
    }
};

class Cat : public Animal {
public:
    void sound() {
        cout << "Cat meows" << endl;
    }
};

int main() {
    Animal* a;   // Base class pointer
    Dog d;
    Cat c;

    a = &d;
    a->sound();   // Calls Dog’s sound()

    a = &c;
    a->sound();   // Calls Cat’s sound()

    return 0;
}

Why Do We Use Pointers in Polymorphism?

When we call a virtual function using an object (e.g., obj.sound()), the compiler already knows its type, so it doesn’t check at runtime. But when we call it through a base class pointer or reference, C++ checks which object the pointer actually refers to and calls the correct version. Hence, pointers or references are used to achieve real dynamic behavior.

Difference Between Function Overloading and Function Overriding

FeatureFunction OverloadingFunction Overriding
TypeCompile-time polymorphismRun-time polymorphism
ParametersMust differMust be same
InheritanceNot requiredRequired
KeywordNo keyword neededUses virtual keyword
Execution TimeDecided at compile timeDecided at runtime

Real-World Example: Draw Function

#include <iostream>
using namespace std;

class Shape {
public:
    virtual void draw() {
        cout << "Drawing a shape" << endl;
    }
};

class Circle : public Shape {
public:
    void draw() {
        cout << "Drawing a circle" << endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() {
        cout << "Drawing a rectangle" << endl;
    }
};

int main() {
    Shape* shape;
    Circle c;
    Rectangle r;

    shape = &c;
    shape->draw();

    shape = &r;
    shape->draw();

    return 0;
}

Output:

Drawing a circle
Drawing a rectangle

Advantages of Polymorphism

  1. Code Reusability: Once a base class or function is written, it can be reused by derived classes without rewriting code. This reduces redundancy and saves development time.
  2. Flexibility: Polymorphism allows one interface to handle multiple types of behavior. For example, a single pointer of a base class can call different functions depending on which derived object it points to.
  3. Scalability: New classes and behaviors can be added easily without modifying existing code. This makes software easier to extend and maintain.
  4. Readability: Code becomes more organized and easier to understand since similar actions are grouped under the same interface or function name.

Key Points to Remember

AspectCompile-TimeRun-Time
How it happensFunction/Operator OverloadingVirtual Functions
Decision TimeBefore program runsDuring program execution
KeywordNonevirtual
Inheritance neededNoYes
Binding TypeEarly bindingLate binding

Exit mobile version