Lesson 3
Understanding Polymorphism in C++ OOP
Introduction

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: A Powerful OOP Principle

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!

Seeing Polymorphism in Action

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.

Dynamic Polymorphism: Runtime Method Overriding

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.

Static Polymorphism: Templates and Function Overloading

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.

Lesson Summary and Practice

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!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.