Object Oriented ProgrammingUnit 613 min read
Polymorphism & Virtual Functions: Runtime Binding, Abstract Classes & Dynamic Dispatch
Unit 6 of Object Oriented Programming explores how polymorphism enables one interface to work with multiple data types at runtime, with a focus on virtual functions, abstract classes, and the mechanics of dynamic method binding. You’ll learn how to design flexible class hierarchies, implement pure virtual functions, an
TAKEAWAYS:
- Runtime polymorphism uses virtual functions to bind method calls at execution time, enabling derived classes to override base class behavior (e.g.,
Animal::sound()→"hiss"forSnake). - Abstract classes (with pure virtual functions) define interfaces without implementations, forcing derived classes to provide concrete methods (e.g.,
Shape::area()must be implemented byCircleorRectangle). - Dynamic dispatch relies on the vtable (virtual table) to resolve function calls at runtime, storing addresses of overridden methods per object.
- Compile-time polymorphism (function overloading) differs from runtime polymorphism by resolving method calls during compilation based on argument types.
- Virtual destructors are critical for safe deletion of derived objects via base pointers to prevent memory leaks.
- Pure virtual functions (
= 0) make a class abstract, while virtual functions (virtual) enable polymorphism in concrete classes.
Core Concepts: What Is Polymorphism?
Polymorphism (Greek for "many forms") allows objects of different classes to be treated uniformly through a common interface. There are two types:
1. Compile-Time Polymorphism (Static Binding)
- Achieved via function overloading or operator overloading.
- The function to call is determined at compile time based on argument types/syntax.
- Example: Overloading
+forintandstring:
Visual: Overloading resolves at compile time like a router directing calls to the correct function based on input types.class Math { public: int add(int a, int b) { return a + b; } string add(string a, string b) { return a + b; } };
2. Runtime Polymorphism (Dynamic Binding)
- Achieved via virtual functions and inheritance.
- The function to call is determined at runtime based on the actual object type.
- Requires:
- A base class with a
virtualfunction. - A derived class overriding the function.
- A base class pointer/reference pointing to a derived object.
- A base class with a
Virtual Functions: How Runtime Polymorphism Works
Mechanism Behind Virtual Functions
When a function is declared virtual in a base class, the compiler:
- Creates a virtual table (vtable) for the class, storing addresses of all virtual functions.
- Each object gets a vptr (virtual pointer) to its class’s vtable.
- At runtime, the vptr directs the call to the correct overridden function.
Example: Animal Sounds
class Animal {
public:
virtual void sound() { cout << "Some sound"; } // Virtual function
};
class Dog : public Animal {
public:
void sound() override { cout << "Raf-raf"; } // Overrides base
};
class Cat : public Animal {
public:
void sound() override { cout << "Meow"; }
};
Trace:
| Step | Code Executed | Output | vptr Points To |
|---|---|---|---|
Animal* a = new Dog(); |
Constructor calls | (None) | Dog::vtable |
a->sound(); |
Dog::sound() via vptr |
"Raf-raf" | Dog::sound |
a = new Cat(); |
Reassigns vptr to Cat |
(None) | Cat::vtable |
a->sound(); |
Cat::sound() via vptr |
"Meow" | Cat::sound |
Visual: The vtable and vptr in action for Dog and Cat:
classDiagram
class Animal {
+virtual void sound()
}
class Dog {
+void sound() "Raf-raf"
}
class Cat {
+void sound() "Meow"
}
Animal <|-- Dog
Animal <|-- Cat
note for Animal "vtable:\n1. sound() -> Animal::sound\n2. vptr -> points to Dog/Cat vtable"
note for Dog "vtable:\n1. sound() -> Dog::sound"
note for Cat "vtable:\n1. sound() -> Cat::sound"Abstract Classes and Pure Virtual Functions
An abstract class cannot be instantiated and is used to define a contract for derived classes.
- Declared with at least one pure virtual function (
= 0). - Forces derived classes to implement the missing methods.
Example: Shape Hierarchy
class Shape {
public:
virtual double area() const = 0; // Pure virtual → abstract
virtual ~Shape() {} // Virtual destructor for safe deletion
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { return 3.14159 * radius * radius; }
};
Real-World Tie-In:
eSewa’s Payment Processing
eSewa uses polymorphism to handle different payment methods (credit card, mobile wallet, bank transfer) under a unified Payment abstract class. Each method (e.g., CreditCardPayment, KhaltiPayment) overrides processPayment(), ensuring consistent billing while adapting to each provider’s rules.
Virtual Destructors: Why They Matter
When deleting a derived object via a base pointer, the derived class destructor must run first to free its resources.
- Solution: Declare the base class destructor
virtual. - Example:
Trace:class Base { public: virtual ~Base() { cout << "Base destructor\n"; } // Virtual! }; class Derived : public Base { public: ~Derived() { cout << "Derived destructor\n"; } };
Visual: Destructor call chain:Base* obj = new Derived(); delete obj; // Output: "Derived destructor\nBase destructor\n"flowchart TD A["delete obj"] --> B["Base::~Base()\n(virtual lookup)"] B --> C["Derived::~Derived()\n(runs first)"] C --> B
classDiagram
class Base {
+virtual ~Base()
}
class Derived {
+~Derived()
}
Base <|-- Derived
note for Base "vtable:
1. ~Base() → Base::~Base()"
note for Derived "vtable:
1. ~Base() → Derived::~Derived()"
note for Derived "Destructor chain:
Derived::~Derived() → Base::~Base()"Destructor call chain with virtual destructorCompile-Time vs. Runtime Polymorphism: Key Differences
| Feature | Compile-Time Polymorphism | Runtime Polymorphism |
|---|---|---|
| Mechanism | Function overloading/operator overloading | Virtual functions + inheritance |
| Binding Time | Compile time | Runtime (dynamic binding) |
| Performance | Faster (no vtable lookup) | Slightly slower (vtable overhead) |
| Syntax | Same function name, different args | Same function name, virtual keyword |
| Example | void print(int) vs void print(double) |
virtual void display() in base class |
| Use Case | Math operations, I/O streams | GUI event handling, plugin systems |
Worked Example: Book Inventory System (Exam-Style)
Problem: A bookshop tracks books with title, price, and stock. Implement a system where:
- Books can be compared by price.
- Use polymorphism to display book details differently for
FictionandNonFiction.
Solution:
class Book {
protected:
string title;
double price;
public:
Book(string t, double p) : title(t), price(p) {}
virtual void display() const { cout << "Title: " << title << ", Price: " << price << endl; }
virtual ~Book() {}
bool operator<(const Book& other) const { return price < other.price; }
};
class Fiction : public Book {
public:
Fiction(string t, double p) : Book(t, p) {}
void display() const override { cout << "Fiction: " << title << " (Price: " << price << ")" << endl; }
};
class NonFiction : public Book {
public:
NonFiction(string t, double p) : Book(t, p) {}
void display() const override { cout << "Non-Fiction: " << title << " (Price: " << price << ")" << endl; }
};
Trace: Comparing two books by price:
Book* b1 = new Fiction("Harry Potter", 500);
Book* b2 = new NonFiction("C++ Primer", 800);
bool cheaper = (*b1 < *b2); // true (500 < 800)
Real-World Tie-In:
Daraz’s Order Queue
Daraz uses polymorphism to prioritize orders. A base Order class with a virtual process() method is overridden by ExpressOrder (high priority) and StandardOrder (normal priority). The system dynamically routes orders to the correct processing queue based on the order type.
Common Pitfalls and Best Practices
- Forgetting
virtual: Withoutvirtual, the base class function is called, not the overridden one.// Wrong: No polymorphism! class Base { void show() { cout << "Base"; } }; class Derived : public Base { void show() { cout << "Derived"; } }; Base* b = new Derived(); b->show(); // Output: "Base" (not "Derived") - Not using
override: Helps catch errors if the base function signature changes. - Memory leaks: Always use virtual destructors when dealing with inheritance.
- Overusing abstract classes: Abstract classes should define interfaces, not implement shared logic (use protected members for that).
In the Real World
eSewa’s Payment Gateway
- Idea Used: Runtime polymorphism via an abstract
PaymentMethodclass. - How: Each payment type (
CreditCard,Khalti,BankTransfer) inherits fromPaymentMethodand overridesprocessPayment(). eSewa dynamically calls the correct method based on user selection. - Code Snippet:
class PaymentMethod { public: virtual bool processPayment(double amount) = 0; virtual ~PaymentMethod() {} }; class KhaltiPayment : public PaymentMethod { public: bool processPayment(double amount) override { // Khalti-specific logic return true; } };
- Idea Used: Runtime polymorphism via an abstract
Ncell’s Billing System
- Idea Used: Polymorphism for different tariff plans.
- How: A base
TariffPlanclass withvirtual calculateBill()is overridden byPrepaidPlanandPostpaidPlan. Ncell’s system routes calls to the correct billing logic at runtime. - Example Trace:
TariffPlan* plan = new PostpaidPlan(1000); // 1000 minutes double bill = plan->calculateBill(); // Calls PostpaidPlan::calculateBill()
Pathao’s Ride Allocation
- Idea Used: Abstract
Vehicleclass with pure virtualcalculateFare(). - How: Derived classes (
Bike,Car,Auto) implement fare calculations differently. Pathao’s dispatcher uses aVehicle*to dynamically invoke the correct fare logic for each ride.
- Idea Used: Abstract
Exam Tip: How to Score Full Marks
Define Key Terms Precisely:
- Polymorphism: "The ability of an interface to be used for a general class of actions."
- Virtual Function: "A member function declared with the
virtualkeyword, enabling dynamic binding." - Abstract Class: "A class with at least one pure virtual function that cannot be instantiated."
Diagrams Are Mandatory:
- Draw vtable diagrams for runtime polymorphism questions.
- Show inheritance hierarchies with overridden methods highlighted.
- Example for a question on
Animalsounds:
Code + Trace = Full Marks:
- Always provide a complete class hierarchy with
virtual/override. - Include a trace table for runtime behavior (e.g., vptr changes).
- Example for a question on function overriding:
Answer Trace:// Question: Explain how this outputs "Derived". class Base { virtual void show() { cout << "Base"; } }; class Derived : public Base { void show() override { cout << "Derived"; } }; int main() { Base* b = new Derived(); b->show(); }Step Action Output Base* b = ...vptr points to Derived::vtable(None) b->show()Calls Derived::show()via vptr"Derived"
- Always provide a complete class hierarchy with
Real-World Applications:
- Link abstract classes to system design (e.g., "A
PaymentProcessorabstract class ensures all payment methods implementvalidate()andcharge()"). - Use Nepali examples (e.g., "NTC’s
ServiceProviderhierarchy uses polymorphism to route calls toLandlineServiceorMobileService").
- Link abstract classes to system design (e.g., "A
Avoid Common Mistakes:
- ❌ Forgetting
virtualin base class → 0 marks for runtime polymorphism. - ❌ Not using
override→ partial marks (shows lack of understanding). - ❌ Memory leaks in destructor questions → deduct marks for unsafe code.
- ❌ Forgetting
Practice Questions (Exam-Style)
Short Answer:
- Differentiate between
virtualandpure virtualfunctions with examples. - Why is a virtual destructor necessary in a base class with derived classes?
- Differentiate between
Programming:
- Implement a
Vehiclehierarchy withCarandBikeclasses. OverridedisplaySpecs()to show different details for each. Use aVehicle*to demonstrate polymorphism.
- Implement a
Trace the Output:
class Base { virtual void show() { cout << "Base"; } }; class Derived : public Base { void show() { cout << "Derived"; } }; int main() { Base* b = new Derived(); b->show(); delete b; }Expected Output:
Derived(with destructors called in reverse order).Design:
- Create an abstract
Employeeclass with pure virtualcalculateSalary(). DeriveFullTimeEmployeeandPartTimeEmployeeto implement it. Explain how this helps HR systems.
- Create an abstract
Based on the TU BSc CSIT syllabus for Object Oriented Programming (CSC166), unit 6.
Discussion
Loading…