Introduction
Polymorphism is one of the four core principles of Object-Oriented Programming (OOP), alongside inheritance, encapsulation, and abstraction. The term polymorphism comes from the Greek words “poly” (many) and “morph” (form), meaning the ability to take many forms. In the context of Object-Oriented System Design (OOSD), polymorphism allows objects to be treated as instances of their parent class, enabling flexibility and dynamic behavior in programs.
In this blog, we’ll delve into the concept of polymorphism in C++, covering its types and usage, and demonstrating how it enhances code reusability and extensibility.
What is Polymorphism?
Polymorphism refers to the ability of different classes to be treated as instances of the same class through inheritance. It allows one interface to be used for a general class of actions, with the specific action determined by the exact nature of the situation.
Polymorphism in C++ enables the same function to behave differently on different classes, making it a powerful tool for designing flexible and scalable software systems.
Types of Polymorphism in C++
Polymorphism in C++ can be broadly categorized into two types:
- Compile-Time Polymorphism (Static Binding)
- Run-Time Polymorphism (Dynamic Binding)
1. Compile-Time Polymorphism (Static Binding)
Compile-time polymorphism, also known as static binding or early binding, occurs when the function to be executed is determined at compile time. This type of polymorphism is typically achieved through function overloading and operator overloading.
a. Function Overloading
Function overloading allows multiple functions with the same name but different parameters to coexist. The compiler determines which function to call based on the arguments passed.
cppCopy code#include <iostream>
using namespace std;
class Calculator {
public:
// Function to add two integers
int add(int a, int b) {
return a + b;
}
// Overloaded function to add three integers
int add(int a, int b, int c) {
return a + b + c;
}
};
int main() {
Calculator calc;
cout << "Sum of two numbers: " << calc.add(10, 20) << endl;
cout << "Sum of three numbers: " << calc.add(10, 20, 30) << endl;
return 0;
}
In this example, the add
function is overloaded to handle both two and three integer inputs. The compiler resolves which version of the function to call based on the number of arguments provided.
b. Operator Overloading
Operator overloading allows C++ operators to be redefined and used with user-defined types, enabling operations with objects in a more intuitive manner.
cppCopy code#include <iostream>
using namespace std;
class Complex {
private:
int real, imag;
public:
Complex(int r = 0, int i = 0) : real(r), imag(i) {}
// Overloading the + operator to add two Complex numbers
Complex operator + (const Complex &obj) {
Complex result;
result.real = real + obj.real;
result.imag = imag + obj.imag;
return result;
}
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex c1(10, 5), c2(2, 4);
Complex c3 = c1 + c2; // Calls overloaded + operator
c3.display();
return 0;
}
In this example, the +
operator is overloaded to add two Complex
numbers, enhancing code readability and functionality.
2. Run-Time Polymorphism (Dynamic Binding)
Run-time polymorphism, also known as dynamic binding or late binding, occurs when the function to be executed is determined during runtime. This is typically achieved through function overriding and the use of virtual functions.
a. Function Overriding
Function overriding allows a derived class to provide a specific implementation of a method that is already defined in its base class. The base class method is replaced by the derived class method when called through an object of the derived class.
b. Virtual Functions and Polymorphism
In C++, virtual functions are used to achieve run-time polymorphism. A virtual function is a member function in the base class that can be overridden in the derived class. When you refer to a derived class object using a pointer or reference to the base class, the overridden function is called, not the base class version.
cppCopy code#include <iostream>
using namespace std;
class Animal {
public:
// Virtual function to allow dynamic binding
virtual void sound() {
cout << "Animal makes a sound." << endl;
}
};
class Dog : public Animal {
public:
// Overriding the base class function
void sound() override {
cout << "Dog barks." << endl;
}
};
class Cat : public Animal {
public:
// Overriding the base class function
void sound() override {
cout << "Cat meows." << endl;
}
};
int main() {
Animal* animalPtr;
Dog dog;
Cat cat;
// Pointing base class pointer to derived class object
animalPtr = &dog;
animalPtr->sound(); // Calls Dog's sound()
animalPtr = &cat;
animalPtr->sound(); // Calls Cat's sound()
return 0;
}
In this example:
- The
sound()
function is declared as a virtual function in theAnimal
class. - The derived classes
Dog
andCat
override thesound()
function. - At runtime, the correct function is called based on the actual object that the pointer points to, enabling dynamic behavior.
Key Concepts in Polymorphism
- Virtual Functions: Allow base class pointers to call overridden functions in derived classes.
- Pure Virtual Functions: A virtual function that has no implementation in the base class. A class containing pure virtual functions is called an abstract class and cannot be instantiated directly.
- Dynamic Binding: The function to be invoked is determined at runtime, allowing for flexible and extendable code.
Conclusion
Polymorphism is a powerful feature of Object-Oriented System Design that promotes flexibility, extensibility, and code reusability. Whether through compile-time polymorphism (function and operator overloading) or run-time polymorphism (virtual functions and overriding), polymorphism enables a single interface to handle different underlying forms, leading to cleaner and more maintainable code.
Mastering polymorphism is essential for designing robust and scalable systems in C++, making it a key concept for any C++ developer.