Elective Object Oriented Programming in C++

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 virtual keyword 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 Functions

Key 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

[object Object][object Object]Shape*CircleSquare
Runtime binding: Base pointer to derived objects (Circle/Square)

How Virtual Functions Work

  1. Declaration: Use the virtual keyword in the base class.
  2. Override: Derived classes provide their own implementation using override (C++11+).
  3. Binding: The correct function is chosen at runtime based on the object's type.
0[object Object]1[object Object]2[object Object]
Virtual table (vtable) mapping function addresses by object 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()!
Shape::~Shape()Circle::~Circle()TOP
Destructor call stack: Only base destructor runs without virtual destructor

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

  1. eSewa Payment Gateway

    • Uses runtime polymorphism to handle different payment methods (Khalti, IME Pay, credit cards).
    • Base class: PaymentMethod with pure virtual processPayment().
    • Derived classes: KhaltiPayment, CreditCardPayment, etc.
    • Example: When a user selects "Pay with Khalti," the system dynamically calls KhaltiPayment::processPayment().
  2. Ncell’s SIM Management System

    • Abstract base class SIMCard with pure virtual activate() and deactivate().
    • Derived classes: PrepaidSIM, PostpaidSIM, CorporateSIM.
    • Example: A new SIM is created as SIMCard* sim = new PrepaidSIM();. The activate() call resolves to PrepaidSIM::activate() at runtime.
  3. Daraz’s Order Processing Queue

    • Uses polymorphism to route orders to different fulfillment centers.
    • Base class: Order with virtual process().
    • Derived classes: StandardOrder, ExpressOrder, InternationalOrder.
    • Example: An ExpressOrder object’s process() 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: double
Class hierarchy for loan polymorphism
class 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

  1. Slicing Problem: Assigning a derived object to a base object (loses polymorphism).
    Circle c;
    Shape s = c; // Slicing! Only Shape part is copied.
    
  2. Forgetting Virtual Destructors: Leads to memory leaks.
  3. Overriding Without override: Risk of accidental mismatches.

✅ Best Practices

  1. Use override: Ensures correct overriding (C++11+).
  2. Prefer Composition Over Inheritance: Reduces tight coupling.
  3. Document Virtual Functions: Clearly state which functions are polymorphic.
  4. Test Polymorphic Code: Verify runtime behavior with multiple derived classes.

Exam Tip

What Examiners Look For

  1. Correct Syntax:
    • virtual keyword in base class.
    • override specifier in derived classes.
    • Pure virtual functions (= 0).
  2. Diagrams:
    • Draw inheritance hierarchies with abstract classes (italicized).
    • Show virtual function tables (vtables) for runtime polymorphism.
  3. Code Traces:
    • Step-by-step execution with object types and method calls.
    • Highlight where binding occurs (compile-time vs. runtime).
  4. Real-World Applications:
    • Link to Nepalese systems (e.g., "How would you design a polymorphic PaymentMethod class for eSewa?").
  5. 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:

  1. Define Shape as abstract base class with pure virtual area().
  2. Derive Circle and Rectangle with overridden area().
  3. Show a vector<Shape*> storing mixed objects.
  4. Trace loop calling shape->area() for each object.
  5. 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…