Skip to main content

3. Runtime Polymorphism

3.1 Virtual Functions

Definition: Functions that can be overridden in derived classes and are resolved at runtime.

#include <iostream>
#include <string>
using namespace std;

class Animal {
protected:
    string name;
    
public:
    Animal(string n) : name(n) {}
    
    // Virtual function
    virtual void makeSound() {
        cout << name << " makes a sound" << endl;
    }
    
    // Non-virtual function
    void eat() {
        cout << name << " is eating" << endl;
    }
    
    virtual ~Animal() {}
};

class Dog : public Animal {
public:
    Dog(string n) : Animal(n) {}
    
    void makeSound() override {
        cout << name << " barks: Woof! Woof!" << endl;
    }
};

class Cat : public Animal {
public:
    Cat(string n) : Animal(n) {}
    
    void makeSound() override {
        cout << name << " meows: Meow! Meow!" << endl;
    }
};

int main() {
    // Runtime polymorphism with pointers
    Animal* animal1 = new Dog("Buddy");
    Animal* animal2 = new Cat("Whiskers");
    
    cout << "=== Using Pointers ===" << endl;
    animal1->makeSound();  // Calls Dog::makeSound()
    animal2->makeSound();  // Calls Cat::makeSound()
    
    animal1->eat();  // Calls Animal::eat() (non-virtual)
    animal2->eat();
    
    delete animal1;
    delete animal2;
    
    // Runtime polymorphism with references
    cout << "\n=== Using References ===" << endl;
    Dog dog("Max");
    Cat cat("Luna");
    
    Animal& ref1 = dog;
    Animal& ref2 = cat;
    
    ref1.makeSound();  // Calls Dog::makeSound()
    ref2.makeSound();  // Calls Cat::makeSound()
    
    return 0;
}

How Virtual Functions Work:

#include <iostream>
using namespace std;

class Base {
public:
    virtual void func1() { cout << "Base::func1()" << endl; }
    virtual void func2() { cout << "Base::func2()" << endl; }
    void func3() { cout << "Base::func3()" << endl; }
};

class Derived : public Base {
public:
    void func1() override { cout << "Derived::func1()" << endl; }
    // func2() not overridden - uses Base version
    void func3() { cout << "Derived::func3()" << endl; }
};

int main() {
    Base* ptr = new Derived();
    
    ptr->func1();  // Derived::func1() - virtual, overridden
    ptr->func2();  // Base::func2() - virtual, not overridden
    ptr->func3();  // Base::func3() - not virtual
    
    delete ptr;
    return 0;
}

3.2 Abstract Classes and Pure Virtual Functions

Definition: A class with at least one pure virtual function. Cannot be instantiated directly.

Syntax:

virtual return_type function_name() = 0;

Example: Payment System

#include <iostream>
#include <string>
using namespace std;

// Abstract base class
class Payment {
protected:
    double amount;
    string transactionId;
    
public:
    Payment(double amt, string id) : amount(amt), transactionId(id) {}
    
    // Pure virtual functions
    virtual void processPayment() = 0;
    virtual void displayReceipt() = 0;
    virtual string getPaymentMethod() = 0;
    
    // Concrete function
    void showAmount() {
        cout << "Amount: $" << amount << endl;
    }
    
    virtual ~Payment() {}
};

class CreditCardPayment : public Payment {
private:
    string cardNumber;
    string cvv;
    
public:
    CreditCardPayment(double amt, string id, string card, string c)
        : Payment(amt, id), cardNumber(card), cvv(c) {}
    
    void processPayment() override {
        cout << "Processing credit card payment..." << endl;
        cout << "Card: ****" << cardNumber.substr(cardNumber.length() - 4) << endl;
        cout << "Payment of $" << amount << " approved!" << endl;
    }
    
    void displayReceipt() override {
        cout << "\n=== Credit Card Receipt ===" << endl;
        cout << "Transaction ID: " << transactionId << endl;
        showAmount();
        cout << "Payment Method: " << getPaymentMethod() << endl;
        cout << "Status: Completed" << endl;
    }
    
    string getPaymentMethod() override {
        return "Credit Card";
    }
};

class PayPalPayment : public Payment {
private:
    string email;
    
public:
    PayPalPayment(double amt, string id, string e)
        : Payment(amt, id), email(e) {}
    
    void processPayment() override {
        cout << "Processing PayPal payment..." << endl;
        cout << "Account: " << email << endl;
        cout << "Payment of $" << amount << " approved!" << endl;
    }
    
    void displayReceipt() override {
        cout << "\n=== PayPal Receipt ===" << endl;
        cout << "Transaction ID: " << transactionId << endl;
        showAmount();
        cout << "PayPal Email: " << email << endl;
        cout << "Payment Method: " << getPaymentMethod() << endl;
        cout << "Status: Completed" << endl;
    }
    
    string getPaymentMethod() override {
        return "PayPal";
    }
};

class BankTransferPayment : public Payment {
private:
    string accountNumber;
    string bankName;
    
public:
    BankTransferPayment(double amt, string id, string acc, string bank)
        : Payment(amt, id), accountNumber(acc), bankName(bank) {}
    
    void processPayment() override {
        cout << "Processing bank transfer..." << endl;
        cout << "Bank: " << bankName << endl;
        cout << "Account: ****" << accountNumber.substr(accountNumber.length() - 4) << endl;
        cout << "Payment of $" << amount << " initiated!" << endl;
        cout << "Note: Transfer may take 1-3 business days" << endl;
    }
    
    void displayReceipt() override {
        cout << "\n=== Bank Transfer Receipt ===" << endl;
        cout << "Transaction ID: " << transactionId << endl;
        showAmount();
        cout << "Bank: " << bankName << endl;
        cout << "Payment Method: " << getPaymentMethod() << endl;
        cout << "Status: Pending" << endl;
    }
    
    string getPaymentMethod() override {
        return "Bank Transfer";
    }
};

// Payment processor using polymorphism
void executePayment(Payment* payment) {
    payment->processPayment();
    payment->displayReceipt();
}

int main() {
    // Cannot instantiate abstract class
    // Payment* p = new Payment(100, "TXN001");  // ERROR!
    
    Payment* payment1 = new CreditCardPayment(250.50, "TXN001", "1234567890123456", "123");
    Payment* payment2 = new PayPalPayment(89.99, "TXN002", "[email protected]");
    Payment* payment3 = new BankTransferPayment(1500.00, "TXN003", "9876543210", "Bank of America");
    
    cout << "=== Payment 1 ===" << endl;
    executePayment(payment1);
    
    cout << "\n=== Payment 2 ===" << endl;
    executePayment(payment2);
    
    cout << "\n=== Payment 3 ===" << endl;
    executePayment(payment3);
    
    delete payment1;
    delete payment2;
    delete payment3;
    
    return 0;
}

3.3 Polymorphism with Arrays

#include <iostream>
#include <vector>
#include <string>
using namespace std;

// Abstract base class
class Employee {
protected:
    string name;
    int id;
    
public:
    Employee(string n, int i) : name(n), id(i) {}
    
    virtual double calculateSalary() = 0;
    virtual void displayInfo() = 0;
    virtual string getType() = 0;
    
    string getName() { return name; }
    
    virtual ~Employee() {}
};

class FullTimeEmployee : public Employee {
private:
    double monthlySalary;
    
public:
    FullTimeEmployee(string n, int i, double salary)
        : Employee(n, i), monthlySalary(salary) {}
    
    double calculateSalary() override {
        return monthlySalary;
    }
    
    void displayInfo() override {
        cout << "Full-Time: " << name << " (ID: " << id << ")" << endl;
        cout << "Monthly Salary: $" << monthlySalary << endl;
    }
    
    string getType() override {
        return "Full-Time";
    }
};

class PartTimeEmployee : public Employee {
private:
    double hourlyRate;
    int hoursWorked;
    
public:
    PartTimeEmployee(string n, int i, double rate, int hours)
        : Employee(n, i), hourlyRate(rate), hoursWorked(hours) {}
    
    double calculateSalary() override {
        return hourlyRate * hoursWorked;
    }
    
    void displayInfo() override {
        cout << "Part-Time: " << name << " (ID: " << id << ")" << endl;
        cout << "Hourly Rate: $" << hourlyRate << ", Hours: " << hoursWorked << endl;
        cout << "Total Pay: $" << calculateSalary() << endl;
    }
    
    string getType() override {
        return "Part-Time";
    }
};

class Contractor : public Employee {
private:
    double projectFee;
    int projectsCompleted;
    
public:
    Contractor(string n, int i, double fee, int projects)
        : Employee(n, i), projectFee(fee), projectsCompleted(projects) {}
    
    double calculateSalary() override {
        return projectFee * projectsCompleted;
    }
    
    void displayInfo() override {
        cout << "Contractor: " << name << " (ID: " << id << ")" << endl;
        cout << "Project Fee: $" << projectFee << ", Projects: " << projectsCompleted << endl;
        cout << "Total Earnings: $" << calculateSalary() << endl;
    }
    
    string getType() override {
        return "Contractor";
    }
};

int main() {
    // Polymorphic array
    vector<Employee*> employees;
    
    employees.push_back(new FullTimeEmployee("Alice Johnson", 101, 5000));
    employees.push_back(new PartTimeEmployee("Bob Smith", 102, 25, 80));
    employees.push_back(new Contractor("Carol White", 103, 3000, 4));
    employees.push_back(new FullTimeEmployee("David Brown", 104, 6000));
    employees.push_back(new PartTimeEmployee("Eve Davis", 105, 30, 60));
    
    cout << "=== All Employees ===" << endl;
    double totalPayroll = 0;
    
    for (Employee* emp : employees) {
        emp->displayInfo();
        totalPayroll += emp->calculateSalary();
        cout << "-----------------------------------" << endl;
    }
    
    cout << "\nTotal Payroll: $" << totalPayroll << endl;
    
    // Count by type
    int fullTime = 0, partTime = 0, contractors = 0;
    for (Employee* emp : employees) {
        string type = emp->getType();
        if (type == "Full-Time") fullTime++;
        else if (type == "Part-Time") partTime++;
        else if (type == "Contractor") contractors++;
    }
    
    cout << "\n=== Employee Distribution ===" << endl;
    cout << "Full-Time: " << fullTime << endl;
    cout << "Part-Time: " << partTime << endl;
    cout << "Contractors: " << contractors << endl;
    
    // Cleanup
    for (Employee* emp : employees) {
        delete emp;
    }
    
    return 0;
}

3.4 Virtual Destructors

Why Virtual Destructors are Important:

#include <iostream>
using namespace std;

// WITHOUT virtual destructor (WRONG)
class BaseWrong {
public:
    BaseWrong() { cout << "Base constructed" << endl; }
    ~BaseWrong() { cout << "Base destructed" << endl; }
};

class DerivedWrong : public BaseWrong {
private:
    int* data;
public:
    DerivedWrong() {
        data = new int[100];
        cout << "Derived constructed" << endl;
    }
    ~DerivedWrong() {
        delete[] data;
        cout << "Derived destructed" << endl;
    }
};

// WITH virtual destructor (CORRECT)
class BaseCorrect {
public:
    BaseCorrect() { cout << "Base constructed" << endl; }
    virtual ~BaseCorrect() { cout << "Base destructed" << endl; }
};

class DerivedCorrect : public BaseCorrect {
private:
    int* data;
public:
    DerivedCorrect() {
        data = new int[100];
        cout << "Derived constructed" << endl;
    }
    ~DerivedCorrect() override {
        delete[] data;
        cout << "Derived destructed" << endl;
    }
};

int main() {
    cout << "=== WITHOUT Virtual Destructor (MEMORY LEAK!) ===" << endl;
    BaseWrong* ptr1 = new DerivedWrong();
    delete ptr1;  // Only Base destructor called!
    
    cout << "\n=== WITH Virtual Destructor (CORRECT) ===" << endl;
    BaseCorrect* ptr2 = new DerivedCorrect();
    delete ptr2;  // Both destructors called!
    
    return 0;
}