Lesson 51 of 60 – Polymorphism in C++
85%

Polymorphism in C++

Polymorphism is one of the most important concepts of Object-Oriented Programming (OOP). The word polymorphism means "many forms". In C++, polymorphism allows the same interface, function, or operation to behave differently in different situations.

Note: C++ mainly supports compile-time polymorphism through function/operator overloading and runtime polymorphism through virtual functions and inheritance.

1. What is Polymorphism?

Polymorphism allows one name or interface to represent different behaviors.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

The sound() function behaves differently for different objects.

2. Meaning of Polymorphism

The term polymorphism comes from two words:

  • Poly = Many
  • Morph = Forms

Therefore, polymorphism means many forms.

For example, a function named draw() may behave differently for a circle, rectangle, and triangle.

3. Types of Polymorphism in C++

C++ commonly uses two major types of polymorphism:

  1. Compile-Time Polymorphism
  2. Runtime Polymorphism

Compile-time polymorphism is resolved during compilation, while runtime polymorphism is resolved while the program is running.

4. Compile-Time Polymorphism

Compile-time polymorphism is determined by the compiler.

Common examples include:

  • Function overloading
  • Operator overloading
void show(int x) {

    std::cout <<
        "Integer";
}

void show(double x) {

    std::cout <<
        "Double";
}

The compiler selects the appropriate function based on the arguments.

5. Function Overloading

Function overloading means defining multiple functions with the same name but different parameter lists.

class Calculator {

public:

    int add(int a, int b) {

        return a + b;
    }

    double add(
        double a,
        double b
    ) {

        return a + b;
    }
};

The compiler determines which version of add() should be called.

6. Function Overloading with Different Parameters

void display(int x) {

    std::cout << x;
}

void display(int x, int y) {

    std::cout <<
        x << " " << y;
}

int main() {

    display(10);

    display(10, 20);

    return 0;
}

The number of parameters is different, so the functions can be overloaded.

7. Operator Overloading

Operator overloading allows operators to work with user-defined objects.

class Number {

public:

    int value;

    Number(int v) {

        value = v;
    }

    Number operator+(
        const Number& other
    ) {

        return Number(
            value + other.value
        );
    }
};

Here, the + operator is given a meaning for Number objects.

8. Runtime Polymorphism

Runtime polymorphism allows the program to select the appropriate overridden function during execution.

It is commonly implemented using:

  • Inheritance
  • Virtual functions
  • Base class pointers or references
class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }
};

9. Virtual Function

A virtual function is a member function declared with the virtual keyword in a base class.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }
};

A derived class can override the virtual function.

10. Function Overriding

Function overriding occurs when a derived class provides its own implementation of a virtual function from the base class.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

11. The override Keyword

The override keyword tells the compiler that a derived class function is intended to override a virtual function.

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

Using override helps detect mistakes in function signatures.

12. Base Class Pointer

A base class pointer can point to a derived class object.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

int main() {

    Dog dog;

    Animal* animal = &dog;

    animal->sound();

    return 0;
}

Because sound() is virtual, the derived implementation is selected.

13. Base Class Reference

A base class reference can also refer to a derived object.

Dog dog;

Animal& animal = dog;

animal.sound();

If sound() is virtual, the derived implementation is called.

14. Why Virtual Functions Are Important

Without a virtual function, a call through a base pointer or reference can resolve to the base-class version rather than the overridden derived version.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }
};

The virtual keyword enables dynamic dispatch for the function.

15. Dynamic Dispatch

Dynamic dispatch means that the function implementation is selected according to the actual object involved in the call.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal";
    }

    virtual ~Animal() = default;
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog";
    }
};

Animal* a = new Dog();

a->sound();

delete a;

The call to sound() uses the Dog implementation.

16. Pure Virtual Function

A pure virtual function is declared by assigning 0 to the function declaration.

class Shape {

public:

    virtual double area() = 0;
};

A class containing a pure virtual function is an abstract class.

17. Abstract Class

An abstract class is a class that cannot normally be instantiated directly. It is often used as a common interface for derived classes.

class Shape {

public:

    virtual double area() = 0;
};

class Circle : public Shape {

public:

    double area() override {

        return 3.14 * 5 * 5;
    }
};

Objects of Circle can be created, but a direct Shape object cannot be created.

18. Practical Shape Example

#include <iostream>

class Shape {

public:

    virtual void draw() = 0;

    virtual ~Shape() = default;
};

class Circle : public Shape {

public:

    void draw() override {

        std::cout <<
            "Drawing Circle";
    }
};

class Rectangle : public Shape {

public:

    void draw() override {

        std::cout <<
            "Drawing Rectangle";
    }
};

int main() {

    Circle circle;

    Rectangle rectangle;

    Shape* s1 = &circle;

    Shape* s2 = &rectangle;

    s1->draw();

    s2->draw();

    return 0;
}

19. Polymorphism with Multiple Derived Classes

class Animal {

public:

    virtual void sound() = 0;

    virtual ~Animal() = default;
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

class Cat : public Animal {

public:

    void sound() override {

        std::cout <<
            "Cat meows";
    }
};

The same sound() interface can represent different behaviors.

20. Runtime Polymorphism with a Function

void makeSound(Animal& animal) {

    animal.sound();
}

int main() {

    Dog dog;

    Cat cat;

    makeSound(dog);

    makeSound(cat);

    return 0;
}

The function accepts an Animal reference but can work with different derived objects.

21. Polymorphism with a Base Pointer

void showSound(Animal* animal) {

    animal->sound();
}

int main() {

    Dog dog;

    Cat cat;

    showSound(&dog);

    showSound(&cat);

    return 0;
}

A base pointer can provide a common interface for different derived objects.

22. Virtual Destructor

When a class is intended to be used polymorphically, its destructor is often declared virtual.

class Animal {

public:

    virtual ~Animal() = default;

    virtual void sound() = 0;
};

This helps ensure that deleting a derived object through a base-class pointer performs the appropriate destruction sequence.

23. Compile-Time vs Runtime Polymorphism

Feature Compile-Time Runtime
Decision During compilation During execution
Common Example Function overloading Virtual functions
Inheritance Required Not necessarily Usually involved
Binding Early binding Dynamic binding

24. Polymorphism and Inheritance

Inheritance provides a relationship between base and derived classes. Polymorphism allows a common interface to work with different derived objects.

class Vehicle {

public:

    virtual void start() {

        std::cout <<
            "Vehicle starts";
    }

    virtual ~Vehicle() = default;
};

class Car : public Vehicle {

public:

    void start() override {

        std::cout <<
            "Car starts";
    }
};

Here, inheritance and virtual functions work together to provide runtime polymorphism.

25. Real-World Example: Payment System

class Payment {

public:

    virtual void pay() = 0;

    virtual ~Payment() = default;
};

class CashPayment : public Payment {

public:

    void pay() override {

        std::cout <<
            "Payment by Cash";
    }
};

class CardPayment : public Payment {

public:

    void pay() override {

        std::cout <<
            "Payment by Card";
    }
};

Different payment classes can provide different implementations of the same pay() interface.

26. Common Polymorphism Mistakes

  • Forgetting the virtual keyword when runtime polymorphism is intended.
  • Using a different function signature when trying to override a function.
  • Forgetting the override keyword.
  • Trying to create an object of an abstract class.
  • Using a non-virtual destructor in an inappropriate polymorphic base class.
  • Confusing function overloading with function overriding.
  • Assuming every inherited function is automatically virtual.

27. Advantages of Polymorphism

  • Flexibility: One interface can work with different object types.
  • Extensibility: New derived classes can be added with less change to common code.
  • Code Reuse: Common interfaces and base functionality can be reused.
  • Maintainability: Common operations can be handled through a common interface.
  • Abstraction: Programs can work with general types instead of specific implementations.

28. Best Practices for Polymorphism

  • Use virtual functions for runtime polymorphic behavior.
  • Use override in derived classes when overriding virtual functions.
  • Use virtual destructors for polymorphic base classes when appropriate.
  • Keep interfaces small and meaningful.
  • Use abstract classes when a common interface is needed.
  • Prefer references or pointers when runtime polymorphism is required.
  • Avoid unnecessary inheritance hierarchies.
  • Use composition when inheritance does not represent a suitable relationship.

29. Real-World Uses of Polymorphism

  • Payment: Cash, Card, and Online payment classes can implement pay().
  • Shape: Circle, Rectangle, and Triangle can implement draw() or area().
  • Vehicle: Car, Bike, and Bus can implement start().
  • Animal: Dog, Cat, and Cow can implement sound().
  • Employee: Different employee types can implement different salary calculations.
  • Notification: Email, SMS, and app notifications can implement send().

30. Polymorphism – Final Summary

Concept Meaning
Polymorphism One interface or name can have many forms.
Compile-Time Polymorphism Behavior is selected during compilation.
Function Overloading Same function name with different parameter lists.
Operator Overloading Giving operators behavior for user-defined types.
Runtime Polymorphism Behavior is selected during execution.
Virtual Function Enables dynamic dispatch through a base interface.
Override Provides a derived implementation of a virtual function.
Pure Virtual Function A virtual function declared with = 0.
Abstract Class A class that cannot be instantiated directly and can define a common interface.
class Animal {

public:

    virtual void sound() = 0;

    virtual ~Animal() = default;
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

class Cat : public Animal {

public:

    void sound() override {

        std::cout <<
            "Cat meows";
    }
};

The same sound() interface can produce different behaviors for different derived classes. This is the core idea of runtime polymorphism.

📌 Key Points

  • Polymorphism means many forms.
  • C++ supports compile-time and runtime polymorphism.
  • Function overloading is a common form of compile-time polymorphism.
  • Operator overloading is another compile-time technique.
  • Runtime polymorphism commonly uses inheritance and virtual functions.
  • The override keyword helps verify overriding.
  • Base class pointers and references can work with derived objects.
  • Pure virtual functions can be used to define abstract interfaces.
  • Abstract classes cannot be instantiated directly.
  • A virtual destructor is important for appropriate polymorphic base classes.

🧠 Quick Quiz

Question: What is the main idea of polymorphism in C++?