Object Oriented Programming in C++Unit 69 min read
Polymorphism & Virtual Functions: Runtime Binding, Overriding & Abstract Classes
Unit 6 of Object Oriented Programming in C++ explores polymorphism (compile-time vs. runtime), virtual functions, abstract classes, and pure virtual functions with code examples, inheritance hierarchies, and real-world applications in Nepalese software systems like eSewa and Ncell.
TAKEAWAYS:
- Polymorphism lets one interface work for multiple types (compile-time via function overloading, runtime via virtual functions).
- Virtual functions enable runtime polymorphism by deferring method binding until object creation.
- Abstract classes (with pure virtual functions) define interfaces but cannot be instantiated.
- The
virtualkeyword and override specifier enforce runtime behavior in inheritance hierarchies. - Virtual destructors are critical for proper cleanup in polymorphic base-class pointers.
- Runtime polymorphism improves flexibility in designing extensible systems like payment gateways or device drivers.
Core Concepts
What is Polymorphism?
Polymorphism (from Greek poly = many, morph = form) allows one interface to represent different underlying forms. In C++, it manifests in two ways:
mindmap
root((Polymorphism in C++))
Compile-Time
Function Overloading
Operator Overloading
Runtime
Virtual Functions
Abstract Classes
Pure Virtual FunctionsKey Idea: At compile-time, the compiler knows the exact function to call. At runtime, the call depends on the actual object type.
Runtime Polymorphism: Virtual Functions
How Virtual Functions Work
- Declaration: Use the
virtualkeyword in the base class. - Override: Derived classes provide their own implementation using
override(C++11+). - Binding: The correct function is chosen at runtime based on the object's type.
class Shape {
public:
virtual void draw() { cout << "Drawing Shape\n"; } // Virtual function
};
class Circle : public Shape {
public:
void draw() override { cout << "Drawing Circle\n"; } // Override
};
Visual: Runtime Binding Process
Trace Example:
Shape* s1 = new Circle();
Shape* s2 = new Square();
s1->draw(); // Output: "Drawing Circle" (runtime decision)
s2->draw(); // Output: "Drawing Square"
Abstract Classes and Pure Virtual Functions
Definitions
- Abstract Class: A class with at least one pure virtual function (
= 0). - Pure Virtual Function: Declared but not defined in the base class (e.g.,
virtual void draw() = 0;).
Why?
- Forces derived classes to implement the interface.
- Cannot instantiate abstract classes (e.g.,
Shape s;is invalid).
class AbstractEmployee {
public:
virtual void work() = 0; // Pure virtual function
virtual ~AbstractEmployee() {} // Virtual destructor
};
Visual: Abstract Class Hierarchy
Virtual Destructors: Critical for Polymorphism
Problem Without Virtual Destructors
Shape* s = new Circle();
delete s; // Calls Shape::~Shape(), not Circle::~Circle()!
Solution: Always declare destructors as virtual in base classes.
class Shape {
public:
virtual ~Shape() {} // Virtual destructor
};
Trace Example:
Shape* shapes[2] = {new Circle(), new Square()};
for (int i = 0; i < 2; i++) {
shapes[i]->draw(); // Polymorphic call
delete shapes[i]; // Correct cleanup due to virtual destructor
}
In the Real World
eSewa Payment Gateway
- Uses runtime polymorphism to handle different payment methods (Khalti, IME Pay, credit cards).
- Base class:
PaymentMethodwith pure virtualprocessPayment(). - Derived classes:
KhaltiPayment,CreditCardPayment, etc. - Example: When a user selects "Pay with Khalti," the system dynamically calls
KhaltiPayment::processPayment().
Ncell’s SIM Management System
- Abstract base class
SIMCardwith pure virtualactivate()anddeactivate(). - Derived classes:
PrepaidSIM,PostpaidSIM,CorporateSIM. - Example: A new SIM is created as
SIMCard* sim = new PrepaidSIM();. Theactivate()call resolves toPrepaidSIM::activate()at runtime.
- Abstract base class
Daraz’s Order Processing Queue
- Uses polymorphism to route orders to different fulfillment centers.
- Base class:
Orderwith virtualprocess(). - Derived classes:
StandardOrder,ExpressOrder,InternationalOrder. - Example: An
ExpressOrderobject’sprocess()is called directly when the user selects "Express Delivery."
Worked Example: Bank Loan Calculator
Scenario: A bank offers different loan types (Home, Car, Personal) with varying interest calculations. Use polymorphism to compute monthly payments.
classDiagram
class Loan {
<<abstract>>
+calculateMonthlyPayment(rate: double): double
+~Loan()
}
class HomeLoan {
+calculateMonthlyPayment(rate: double): double
}
class CarLoan {
+calculateMonthlyPayment(rate: double): double
}
Loan <|-- HomeLoan
Loan <|-- CarLoan
Loan : principal: double
HomeLoan : principal: double
CarLoan : principal: doubleClass hierarchy for loan polymorphismclass Loan {
protected:
double principal;
public:
Loan(double p) : principal(p) {}
virtual double calculateMonthlyPayment(double rate) = 0;
virtual ~Loan() {}
};
class HomeLoan : public Loan {
public:
HomeLoan(double p) : Loan(p) {}
double calculateMonthlyPayment(double rate) override {
return principal * rate / 12; // Simplified formula
}
};
class CarLoan : public Loan {
public:
CarLoan(double p) : Loan(p) {}
double calculateMonthlyPayment(double rate) override {
return (principal * rate / 12) * 1.1; // Higher fee for cars
}
};
Trace:
| Step | Action | Output |
|---|---|---|
| 1 | Loan* loan1 = new HomeLoan(500000); |
Object created |
| 2 | loan1->calculateMonthlyPayment(0.05); |
Calls HomeLoan::calculateMonthlyPayment() |
| 3 | Calculation: 500000 * 0.05 / 12 |
Returns 2083.33 |
| 4 | Loan* loan2 = new CarLoan(200000); |
Object created |
| 5 | loan2->calculateMonthlyPayment(0.06); |
Calls CarLoan::calculateMonthlyPayment() |
| 6 | Calculation: (200000 * 0.06 / 12) * 1.1 |
Returns 1100.00 |
Comparison Table: Polymorphism Types
| Feature | Compile-Time Polymorphism | Runtime Polymorphism |
|---|---|---|
| Mechanism | Function/operator overloading | Virtual functions |
| Binding Time | Compile-time | Runtime |
| Performance | Faster (no overhead) | Slight overhead (vtable) |
| Syntax | Same function name, different parameters | virtual keyword + override |
| Example | void print(int x); and void print(double x); |
virtual void draw(); in base class |
| Use Case | Math operations, constructors | GUI frameworks, plugin systems |
Common Pitfalls and Best Practices
❌ Anti-Patterns
- Slicing Problem: Assigning a derived object to a base object (loses polymorphism).
Circle c; Shape s = c; // Slicing! Only Shape part is copied. - Forgetting Virtual Destructors: Leads to memory leaks.
- Overriding Without
override: Risk of accidental mismatches.
✅ Best Practices
- Use
override: Ensures correct overriding (C++11+). - Prefer Composition Over Inheritance: Reduces tight coupling.
- Document Virtual Functions: Clearly state which functions are polymorphic.
- Test Polymorphic Code: Verify runtime behavior with multiple derived classes.
Exam Tip
What Examiners Look For
- Correct Syntax:
virtualkeyword in base class.overridespecifier in derived classes.- Pure virtual functions (
= 0).
- Diagrams:
- Draw inheritance hierarchies with abstract classes (italicized).
- Show virtual function tables (vtables) for runtime polymorphism.
- Code Traces:
- Step-by-step execution with object types and method calls.
- Highlight where binding occurs (compile-time vs. runtime).
- Real-World Applications:
- Link to Nepalese systems (e.g., "How would you design a polymorphic
PaymentMethodclass for eSewa?").
- Link to Nepalese systems (e.g., "How would you design a polymorphic
- Edge Cases:
- Virtual destructors, slicing, and abstract class instantiation.
Sample Exam Question Breakdown:
"Explain runtime polymorphism in C++ with a program to compute the area of different shapes (Circle, Rectangle). Use virtual functions and include a virtual destructor."
Expected Answer Structure:
- Define
Shapeas abstract base class with pure virtualarea(). - Derive
CircleandRectanglewith overriddenarea(). - Show a
vector<Shape*>storing mixed objects. - Trace loop calling
shape->area()for each object. - Include virtual destructor in
Shape.
Visual Summary:
Based on the PU BE Computer (PU) syllabus for Object Oriented Programming in C++, unit 6.
Discussion
Loading…