Polymorphism in C++

Polymorphism is a fundamental concept in object-oriented programming (OOP) that allows objects of different classes to be treated as objects of a common base class. It enables code to be written in a more generic and flexible way, making it easier to maintain and extend. There are two types of polymorphism in C++: compile-time (static) polymorphism and runtime (dynamic) polymorphism.

  1. Compile-time (Static) Polymorphism:
    Compile-time polymorphism is achieved through function overloading and operator overloading. In both cases, the decision of which function or operator to call is made at compile time based on the number or types of arguments used.

Function Overloading:
Function overloading allows multiple functions with the same name but different parameters to be defined in a class. The appropriate function to call is determined during compilation based on the number and types of arguments passed.

Syntax for function overloading:

class ClassName {
public:
    returnType functionName(parameterType1 param1, parameterType2 param2, ...);
};

Example of function overloading:

#include <iostream>

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

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

int main() {
    MathOperations math;

    std::cout << math.add(5, 10) << std::endl;         // Calls int add(int a, int b)
    std::cout << math.add(3.14, 2.71) << std::endl;   // Calls double add(double a, double b)

    return 0;
}
  1. Runtime (Dynamic) Polymorphism:
    Runtime polymorphism is achieved through inheritance and virtual functions. It allows a derived class to provide a specific implementation for a function defined in the base class. The decision of which function to call is made at runtime based on the actual type of the object.

Virtual Functions:
A virtual function is a function declared in the base class with the virtual keyword and can be overridden in derived classes using the override keyword (C++11 and later) or by using the same function signature in the derived class.

Syntax for virtual functions:

class BaseClass {
public:
    virtual returnType functionName(parameterType1 param1, parameterType2 param2, ...) {
        // Function body
    }
};

class DerivedClass : public BaseClass {
public:
    returnType functionName(parameterType1 param1, parameterType2 param2, ...) override {
        // Function body (specific implementation in the derived class)
    }
};

Example of runtime polymorphism:

#include <iostream>

class Shape {
public:
    virtual double area() {
        return 0.0;
    }
};

class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double area() override {
        return 3.14 * radius * radius;
    }
};

class Square : public Shape {
private:
    double side;

public:
    Square(double s) : side(s) {}

    double area() override {
        return side * side;
    }
};

int main() {
    Shape* shape1 = new Circle(5.0);
    Shape* shape2 = new Square(4.0);

    std::cout << "Area of Circle: " << shape1->area() << std::endl; // Calls Circle's area()
    std::cout << "Area of Square: " << shape2->area() << std::endl; // Calls Square's area()

    delete shape1;
    delete shape2;

    return 0;
}

In this example, we have a base class Shape with a virtual function area(). Two derived classes Circle and Square inherit from Shape and provide specific implementations for the area() function. At runtime, when calling the area() function through base class pointers shape1 and shape2, the appropriate function implementation from the derived classes is invoked based on the actual object types. This is the essence of runtime polymorphism.