Greetings! In today's lesson, we'll explore the concept of polymorphism in C++'s Object-Oriented Programming (OOP). Understanding polymorphism empowers us to utilize a single interface to represent different types in various situations. Let's get started.
Polymorphism, one of the core principles of OOP, allows an object to take on multiple forms. Imagine a button in software; its behavior changes based on its type (e.g., a submit button versus a radio button). This dynamic showcases the essence of polymorphism!
Let's observe polymorphism in action through a simple application involving shapes. In the base Shape
class, we define a pure virtual area
method and a virtual destructor. The area
method is uniquely implemented in the derived classes Rectangle
and Circle
, while the virtual destructor ensures that the destructors of derived classes are called appropriately, preventing resource leaks.
C++1#include <iostream> 2using namespace std; 3 4class Shape { 5public: 6 virtual ~Shape() {} // virtual destructor 7 virtual double area() const = 0; // pure virtual function 8}; 9 10class Rectangle : public Shape { 11private: 12 double length, width; 13public: 14 Rectangle(double l, double w) : length(l), width(w) {} 15 16 double area() const override { // calculate rectangle area 17 return length * width; 18 } 19}; 20 21class Circle : public Shape { 22private: 23 double radius; 24public: 25 Circle(double r) : radius(r) {} 26 27 double area() const override { // calculate circle area 28 return 3.14 * radius * radius; 29 } 30}; 31 32int main() { 33 Rectangle rectangle(2, 3); 34 cout << rectangle.area() << endl; // Outputs: 6 35 36 Circle circle(5); 37 cout << circle.area() << endl; // Outputs: 78.5 38 39 return 0; 40}
In this example, polymorphism is demonstrated as the area
function takes multiple forms and behaves differently based on whether it's attached to a Rectangle
or a Circle
. The inclusion of a virtual destructor in the Shape
class ensures that when a derived object is deleted through a base pointer, the correct destructor is invoked, reflecting proper resource management and preventing memory leaks.
C++ supports polymorphism through the use of virtual functions. Dynamic polymorphism occurs during runtime and involves method overriding within base and derived classes. Here's an example of implementing a simple shape hierarchy using virtual functions:
C++1#include <iostream> 2using namespace std; 3 4class Shape { 5public: 6 virtual double area() const { 7 return 0; 8 } 9}; 10 11class Rectangle : public Shape { 12private: 13 double length, width; 14public: 15 Rectangle(double l, double w) : length(l), width(w) {} 16 17 double area() const override { 18 return length * width; 19 } 20}; 21 22class Circle : public Shape { 23private: 24 double radius; 25public: 26 Circle(double r) : radius(r) {} 27 28 double area() const override { 29 return 3.14 * radius * radius; 30 } 31}; 32 33void printArea(const Shape& shape) { 34 cout << "Area: " << shape.area() << endl; 35} 36 37int main() { 38 Shape* rectangle = new Rectangle(2, 3); 39 Shape* circle = new Circle(5); 40 printArea(*rectangle); // Output: Area: 6 41 printArea(*circle); // Output: Area: 78.5 42 return 0; 43}
In the above snippet, the printArea
function exemplifies the added value of polymorphism by allowing us to pass any derived Shape
object and print its area without knowing the object's specific type.
Apart from dynamic polymorphism, C++ also supports static polymorphism, which is resolved at compile time. Two popular mechanisms to achieve static polymorphism are function overloading and templates.
Function overloading allows functions to have the same name but with different parameter types or numbers:
C++1#include <iostream> 2using namespace std; 3 4// Overloaded functions for displaying details 5void display(int i) { 6 cout << "Integer: " << i << endl; 7} 8 9void display(double d) { 10 cout << "Double: " << d << endl; 11} 12 13int main() { 14 display(5); // Calls display(int) 15 display(3.14); // Calls display(double) 16 17 return 0; 18}
In this example, the display
function is overloaded to handle both integers and doubles, demonstrating static polymorphism by resolving the correct function at compile time.
On the other hand, Templates enable generic programming, where functions or classes can operate with different data types:
C++1#include <iostream> 2using namespace std; 3 4template <typename T> 5T add(T a, T b) { 6 return a + b; 7} 8 9int main() { 10 cout << add(3, 4) << endl; // Outputs: 7 11 cout << add(2.5, 4.3) << endl; // Outputs: 6.8 12 13 return 0; 14}
Here, the add
function template can accept parameters of any type, allowing operations on both integers and floating-point numbers.
The usage of both dynamic and static polymorphism promotes versatility in our code, enabling us to write flexible and reusable functions and classes.
Excellent work! We've explored the concept of polymorphism, its implementation in C++, and discussed when and why to use it. Now, you're encouraged to engage in hands-on practice. Try creating your own class hierarchy implementing polymorphic behavior, and continue to develop your C++ skills. Happy coding!