Welcome to the world of C++ polymorphism! Today, we delve into the intriguing concepts of Compile-Time and Runtime Polymorphism. Polymorphism allows objects to take on multiple forms, and in C++, we have two types: Compile-Time and Runtime. In this article, we’ll explore the need for polymorphism, how it works, and how it benefits our programs. With Compile-Time Polymorphism, we can achieve method overloading using function signatures, while with Runtime Polymorphism, we can leverage virtual functions and dynamic binding. Understanding these polymorphic techniques will empower you to write more flexible and robust C++ code. Let’s dive in!
Table of Contents
- What is Polymorphism in C++?
- Compile-time Polymorphism vs Runtime Polymorphism
- Compile-time Polymorphism
- Runtime Polymorphism
- Understand by Detailed Diagram
- Differences between Compile-time Polymorphism and Runtime Polymorphism
- Real-life Examples
- Pros and Cons of Compile-time and Runtime Polymorphism
- Key Takeaways
- FAQs
What is Polymorphism in C++?
Polymorphism in C++ is like giving different meanings to the same word based on the context. In programming, it means the ability of different classes to be treated as instances of a common base class. Imagine you have different types of shapes like circles, squares, and triangles, but you want to work with all of them using a common approach.
Polymorphism lets you do just that. You can create a common interface, like a “draw” function, in a base class. Then, each specific shape class can provide its own version of the “draw” function, tailored to its shape.
For example, when you call the “draw” function on a circle, it will draw a circle. But if you call the same function on a square, it will draw a square. The magic happens because of virtual functions and pointers or references to the base class. This way, you can work with different objects in a unified way, even if they have different behaviors.
In everyday terms, think of a TV remote. The buttons do different things depending on whether you’re controlling a TV, a DVD player, or a streaming device. You’re using the same remote interface to control different devices. Similarly, polymorphism lets you interact with various objects through a common interface, letting them respond based on their unique nature.
Compile-time Polymorphism vs Runtime Polymorphism
There are two types of polymorphism in C++: compile-time polymorphism and runtime polymorphism.
Compile-time Polymorphism
Compile-time polymorphism in C++ is a concept that allows you to achieve different behaviors for the same function name at compile time. It’s also known as method overloading and operator overloading. This happens because the compiler determines which function to call based on the number and types of arguments passed to it. This way, you can reuse function names and make your code more readable and concise.
Method Overloading:
Method overloading occurs when multiple functions with the same name exist in a class, but they differ in terms of the type or number of parameters they accept. The compiler decides which function to call based on the arguments you provide.
#include
using namespace std;
class Calculator {
public:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
};
int main() {
Calculator calc;
cout << calc.add(5, 10) << endl; // Calls int add(int a, int b)
cout << calc.add(3.5, 7.2) << endl; // Calls double add(double a, double b)
return 0;
}
Output:
15
10.7
Operator Overloading:
Operator overloading allows you to define new behaviors for the standard operators like +, -, *, etc. You can create custom implementations for these operators within a class.
#include
using namespace std;
class Complex {
private:
double real;
double imag;
public:
Complex(double r, double i) : real(r), imag(i) {}
Complex operator+(const Complex& other) {
return Complex(real + other.real, imag + other.imag);
}
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex num1(3.0, 4.0);
Complex num2(1.5, 2.5);
Complex sum = num1 + num2; // Calls the overloaded + operator
sum.display(); // Outputs: 4.5 + 6.5i
return 0;
}
Output:
4.5 + 6.5i
Runtime Polymorphism
Runtime polymorphism in C++ is a powerful concept that allows different classes to be treated as instances of a common base class. This enables more flexible and dynamic behavior in your code. This concept is closely related to inheritance and is achieved through the use of virtual functions.
In simple terms, runtime polymorphism allows you to call methods of a derived class through a pointer or reference of the base class, and the appropriate method of the derived class is executed based on the actual object pointed to or referred to, rather than the type of the pointer or reference itself.
Here are the key points to understand about runtime polymorphism:
- Base Class and Derived Class: In this approach, you have a base class and one or more derived classes. The base class has virtual functions, and these functions are overridden by the derived classes to provide specific implementations.
- Virtual Function: A virtual function is a member function in the base class that is marked with the
virtual
keyword. This indicates that the function can be overridden by derived classes. - Dynamic Binding: The decision about which function to call is made during runtime, based on the actual type of the object being referred to, rather than the type of the reference or pointer.
Here’s a simple example to illustrate runtime polymorphism:
#include
using namespace std;
class Shape {
public:
virtual void displayArea() {
cout << "Area of Shape" << endl;
}
};
class Circle : public Shape {
public:
void displayArea() override {
cout << "Area of Circle" << endl;
}
};
class Rectangle : public Shape {
public:
void displayArea() override {
cout << "Area of Rectangle" << endl;
}
};
int main() {
Shape* s1 = new Circle();
Shape* s2 = new Rectangle();
s1->displayArea(); // Output: Area of Circle
s2->displayArea(); // Output: Area of Rectangle
delete s1;
delete s2;
return 0;
}
Output:
Area of Circle
Area of Rectangle
Explanation:
- Base Class and Derived Classes: The code features a base class named “Shape” and two derived classes, “Circle” and “Rectangle,” all linked by inheritance.
- Virtual Function: The base class has a virtual function called “displayArea()” that is overridden in the derived classes with specific implementations.
- Polymorphic Behavior: Using base class pointers, instances of “Circle” and “Rectangle” are created in the “main()” function. When calling “displayArea()” through these pointers, the correct overridden method is executed based on the actual object’s type.
- Common Interface: This approach lets you treat objects of different types uniformly through a shared interface, even though they have diverse implementations.
- Flexibility and Simplification: Runtime polymorphism enhances code flexibility by enabling dynamic method calls and streamlines handling various objects with a unified approach, making code management easier.
Understand by Detailed Diagram
Compile-time Polymorphism
- Also known as Static Polymorphism.
- Resolved at compile time.
- Achieved through function overloading and operator overloading.
- Example:
void fun(int i) { }
andvoid fun(double d) { }
.
Runtime Polymorphism
- Also known as Dynamic Polymorphism.
- Resolved at runtime.
- Achieved through virtual functions and pointers.
- Example:
class Base { public: virtual void show() { } }; class Derived : public Base { public: void show() { } };
Differences between Compile-time Polymorphism and Runtime Polymorphism
Aspect | Compile-time Polymorphism | Runtime Polymorphism |
---|---|---|
Also Known As | Static Polymorphism, Method Overloading | Dynamic Polymorphism, Method Overriding |
Implementation Decision | Made by the compiler at compile time | Determined at runtime |
Method Selection | Based on the number and type of arguments | Based on the actual object type |
Function Overloading | Multiple functions with the same name | The derived class overrides the base class method |
Mechanism | Function overloading, Operator overloading | Virtual functions and inheritance |
Example | Multiple constructor overloads in a class | Overriding a base class method in a subclass |
Performance Overhead | None, resolved at compile time | Slightly slower due to method resolution |
Flexibility | Limited, as decisions are fixed at compile time | High, adaptable to dynamic scenarios |
Method Binding | Early binding, also known as static binding | Late binding, also known as dynamic binding |
Syntax | Functions/methods with the same name but different parameters | Functions/methods with the same name and parameters, but different implementations |
Use Cases | Method overloading, operator overloading | Method overriding, polymorphic behavior |
Real-life Examples
Let’s look at some examples to understand these concepts better.
Compile-time Polymorphism Example
#include
using namespace std;
class Party {
public:
void inviteFriends(string name) {
cout << "Calling " << name << endl;
}
void inviteFriends(string name, string method) {
cout << "Inviting " << name << " by " << method << endl;
}
};
int main() {
Party p;
p.inviteFriends("John");
p.inviteFriends("Sarah", "email");
return 0;
}
Output:
Calling John
Inviting Sarah by email
Explanation
- The code defines a class called “Party” that has two overloaded functions: “inviteFriends” with one parameter and “inviteFriends” with two parameters.
- The first “inviteFriends” function takes a string parameter representing the name of a friend and outputs a message saying that the person is being called.
- The second “inviteFriends” function takes two string parameters, the name of a friend and the method of invitation, and outputs a message saying that the person is being invited using the specified method.
- In the “main” function, an object of the “Party” class is created.
- The “inviteFriends” functions are called with different arguments to demonstrate the different ways of inviting friends, and appropriate messages are printed based on the provided arguments.
Runtime Polymorphism Example
#include
using namespace std;
class Gift {
public:
virtual void openGift() {
cout << "It's a gift!" << endl;
}
};
class Book : public Gift {
public:
void openGift() override {
cout << "It's a book!" << endl;
}
};
class Toy : public Gift {
public:
void openGift() override {
cout << "It's a toy!" << endl;
}
};
int main() {
Gift* g;
Book b;
Toy t;
g = &b;
g->openGift();
g = &t;
g->openGift();
return 0;
}
Output:
It's a book!
It's a toy!
Explanation:
- The code uses polymorphism with virtual functions to demonstrate different behaviors based on the object type.
- The
Gift
class has a virtual functionopenGift()
that can be overridden by derived classes. - The
Book
andToy
classes inherit fromGift
and provide their own implementations ofopenGift()
. - The
main()
function creates objects ofBook
andToy
, assigns them to aGift*
pointer, and callsopenGift()
. - Polymorphism allows the appropriate version of
openGift()
to be executed based on the actual object type. - The output shows “It’s a book!” and “It’s a toy!” to demonstrate the dynamic behavior of polymorphism.
Pros and Cons of Compile-time and Runtime Polymorphism
Compile-time Polymorphism | Runtime Polymorphism | |
---|---|---|
Binding Time | Early Binding (Static Binding) | Late Binding (Dynamic Binding) |
Decided at | Compile Time | Runtime |
Function Overloading | Achieved through function overloading | Not applicable |
Function Overriding | Not applicable | Achieved through virtual functions |
Flexibility | Limited flexibility in changing behavior | High flexibility in changing behavior |
Efficiency | More efficient | Slightly less efficient |
Example | Function Overloading | Function Overriding using virtual functions |
Key Takeaways
- Polymorphism’s Unified Interface: Polymorphism in C++ enables a single interface to handle diverse actions, enhancing code flexibility.
- Compile-Time Polymorphism: Function overloading and operator overloading are examples of compile-time polymorphism, resolved during compilation.
- Runtime Polymorphism: Function overriding and virtual functions exemplify runtime polymorphism, determined during program execution.
- Important Distinction: Understanding the contrast between compile-time and runtime polymorphism is essential for effective C++ programming.
- Practice and Proficiency: With practice, mastering polymorphism in C++ will empower you to write adaptable and efficient code.
FAQs
1. What is Compile-time Polymorphism in C++?
Compile-time polymorphism, also known as static or early binding, refers to the situation where the function to be executed is determined at compile time. It is achieved through function overloading and operator overloading.
2. What is Runtime Polymorphism in C++?
Runtime polymorphism, also known as dynamic or late binding, occurs when the function to be executed is not known at compile time but is determined at runtime. It is achieved through function overriding and virtual functions.
3. How does Compile-time Polymorphism work?
Compile-time polymorphism uses function overloading to define multiple functions with the same name but different parameters. The appropriate function is selected based on the number or types of arguments passed during compilation.
4. How does Runtime Polymorphism work?
Runtime polymorphism uses function overriding and virtual functions. When a function is declared as virtual in the base class, it allows derived classes to provide their implementation. The actual function called is determined based on the object’s type during runtime.
5. What are the advantages of Compile-time and Runtime Polymorphism?
Compile-time polymorphism offers better performance and efficiency as the function is known at compile time, leading to optimized code execution. On the other hand, runtime polymorphism provides greater flexibility, allowing dynamic changes in behavior at runtime, making the code more adaptable and maintainable.