Polymorphism, derived from Greek meaning "many shapes", is a fundamental concept in object-oriented programming (OOP). It allows objects of different classes to be treated as objects of a common superclass. In C++, polymorphism can be achieved through function overloading, function overriding, and using virtual functions.
Function overloading allows you to define multiple functions with the same name but different parameters. The compiler determines which function to call based on the number and type of arguments passed.
#include
void display(int num) {
std::cout << "Integer: " << num << std::endl;
}
void display(double num) {
std::cout << "Double: " << num << std::endl;
}
int main() {
display(5); // Calls display(int)
display(3.14); // Calls display(double)
return 0;
}
// output //
Integer: 5
Double: 3.14
Templates in C++ provide a way to create functions and classes that work with any data type. They allow you to write generic code that can be reused with different data types without having to rewrite the code for each type.
Function templates allow you to define a function template once and use it with different data types. They are declared using the template
keyword followed by a list of template parameters enclosed in angle brackets (<>
).
#include
template
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(5, 3) << std::endl; // Calling with integers
std::cout << add(3.5, 2.5) << std::endl; // Calling with doubles
return 0;
}
// output //
8
6
In this example, add()
is a function template that can add two values of any type (int
, double
, etc.) because it’s parameterized with the template parameter T
.
Note : We will discuss more about templates further in detail with individual topic
Virtual functions allow you to achieve run-time polymorphism by enabling dynamic method binding. They are functions declared in a base class and overridden in derived classes.
#include
class Animal {
public:
virtual void sound() {
std::cout << "Animal makes a sound" << std::endl;
}
};
class Dog : public Animal {
public:
void sound() override {
std::cout << "Dog barks" << std::endl;
}
};
class Cat : public Animal {
public:
void sound() override {
std::cout << "Cat meows" << std::endl;
}
};
int main() {
Animal* ptr;
Dog dog;
Cat cat;
ptr = &dog;
ptr->sound(); // Calls Dog's sound()
ptr = &cat;
ptr->sound(); // Calls Cat's sound()
return 0;
}
// output //
Dog barks
Cat meows
Consider a scenario where you have a base class Shape
and derived classes Rectangle
and Circle
. Each derived class implements its own version of the area()
function.
#include
class Shape {
public:
virtual float area() {
return 0;
}
};
class Rectangle : public Shape {
private:
float length;
float width;
public:
Rectangle(float l, float w) : length(l), width(w) {}
float area() override {
return length * width;
}
};
class Circle : public Shape {
private:
float radius;
public:
Circle(float r) : radius(r) {}
float area() override {
return 3.14 * radius * radius;
}
};
int main() {
Shape* shapes[2];
shapes[0] = new Rectangle(5, 3);
shapes[1] = new Circle(4);
for (int i = 0; i < 2; ++i) {
std::cout << "Area of shape " << i+1 << ": " << shapes[i]->area() << std::endl;
delete shapes[i];
}
return 0;
}
// output //
Area of shape 1: 15
Area of shape 2: 50.24
Abstract classes are classes that cannot be instantiated and may contain one or more pure virtual functions. A pure virtual function is a virtual function with no implementation in the base class. It must be overridden in derived classes.
#include
class Shape {
public:
virtual float area() = 0; // Pure virtual function
};
class Rectangle : public Shape {
private:
float length;
float width;
public:
Rectangle(float l, float w) : length(l), width(w) {}
float area() override {
return length * width;
}
};
int main() {
// Shape shape; // Error: Cannot instantiate abstract class
Rectangle rectangle(5, 3);
std::cout << "Area of rectangle: " << rectangle.area() << std::endl;
return 0;
}
// output //
Area of rectangle: 15
area()
, denoted by = 0
.area()
function is declared without a body, making Shape
an abstract class. Abstract classes cannot be instantiated; they are meant to serve as base classes for other classes.Shape
class.area()
function to calculate the area of a rectangle by multiplying its length and width.Shape shape;
), so it’s commented out. This line would result in a compilation error.Rectangle
class is created with a length of 5 and a width of 3.area()
method of the Rectangle
class is called to calculate the area of the rectangle.When working with polymorphism and inheritance, it’s important to declare the base class destructor as virtual. This ensures that the appropriate destructor is called when deleting objects through base class pointers.
#include
class Base {
public:
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() override {
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // Calls Derived destructor
return 0;
}
// output //
Derived destructor
Base destructor
Derived
class because ptr
points to an object of the Derived
class. This prints “Derived destructor”.Base
class (the base class destructor is called automatically after the derived class destructor) because the Base
class destructor is marked as virtual. This prints “Base destructor”.Polymorphism can also be applied to exception handling. You can define an exception hierarchy with base and derived exception classes, allowing for more specific error handling.
#include
#include
class BaseException : public std::exception {
public:
const char* what() const noexcept override {
return "Base Exception";
}
};
class DerivedException : public BaseException {
public:
const char* what() const noexcept override {
return "Derived Exception";
}
};
int main() {
try {
throw DerivedException();
} catch (BaseException& e) {
std::cout << "Caught: " << e.what() << std::endl;
}
return 0;
}
// output //
Caught: Derived Exception
DerivedException
, it is caught by reference to BaseException
in the catch
block.BaseException
is the base class of DerivedException
, polymorphism allows the catch
block to catch the exception object.e.what()
is called inside the catch
block, it resolves to the overridden what()
method of DerivedException
, printing “Derived Exception”.Polymorphism in C++ allows for flexibility and extensibility in code. By leveraging compile-time and run-time polymorphism, you can write more modular, maintainable, and scalable programs. Function overloading and templates enable compile-time polymorphism, while virtual functions and inheritance facilitate run-time polymorphism, making C++ a powerful language for object-oriented programming.Happy coding !❤️