Polymorphism
Polymorphism and Virtual Functions
Section titled “Polymorphism and Virtual Functions”Polymorphism is one of the four pillars of OOP, meaning “many forms.” It allows objects of different classes to be treated as objects of a common superclass. This chapter covers how to achieve polymorphism in C++ using virtual functions.
Types of Polymorphism
Section titled “Types of Polymorphism”┌─────────────────────────────────────────────────────────────┐│ Types of Polymorphism │├─────────────────────────────────────────────────────────────┤│ ││ Compile-time (Static) Runtime (Dynamic) ││ ┌──────────────────┐ ┌──────────────────┐ ││ │ 1. Function │ │ 1. Virtual │ ││ │ Overloading │ │ Functions │ ││ │ │ │ │ ││ │ 2. Operator │ │ 2. Inheritance │ ││ │ Overloading │ │ (Subtyping) │ ││ └──────────────────┘ └──────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘Virtual Functions
Section titled “Virtual Functions”A virtual function is a member function that can be overridden in derived classes:
class Animal {public: // Virtual function - can be overridden virtual void speak() { std::cout << "Animal speaks\n"; }
// Virtual destructor (important!) virtual ~Animal() {}};
class Dog : public Animal {public: void speak() override { // Overrides Animal::speak std::cout << "Dog barks: Woof!\n"; }};
class Cat : public Animal {public: void speak() override { std::cout << "Cat meows: Meow!\n"; }};
int main() { Animal* animal1 = new Dog(); Animal* animal2 = new Cat();
animal1->speak(); // Calls Dog::speak animal2->speak(); // Calls Cat::speak
delete animal1; delete animal2;}How Virtual Functions Work (vtable)
Section titled “How Virtual Functions Work (vtable)”Behind the scenes, virtual functions use a virtual table (vtable):
┌─────────────────────────────────────────────────────────────┐│ Virtual Table (vtable) │├─────────────────────────────────────────────────────────────┤│ ││ Animal Object Dog Object Cat Object ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ vptr ───────┼───────│ vptr ───────┼─────│ vptr ───────│ ││ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ ││ │ │ │ ││ ▼ ▼ ▼ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ Animal:: │ │ Dog:: │ │ Cat:: │ ││ │ speak() │ │ speak() │ │ speak() │ ││ └─────────────┘ └─────────────┘ └─────────────┘ ││ │└─────────────────────────────────────────────────────────────┘Pure Virtual Functions
Section titled “Pure Virtual Functions”A pure virtual function has no implementation in the base class:
class Shape {public: // Pure virtual = abstract method virtual double area() const = 0; virtual void draw() const = 0;
virtual ~Shape() {}};
// Cannot create Shape objects - it's abstract// Shape s; // Error!
class Circle : public Shape {private: double radius;
public: Circle(double r) : radius(r) {}
double area() const override { return 3.14159 * radius * radius; }
void draw() const override { std::cout << "Drawing circle\n"; }};Abstract classes cannot be instantiated - they’re just blueprints.
Override Keyword (C++11)
Section titled “Override Keyword (C++11)”Use override to ensure you’re actually overriding a virtual function:
class Base {public: virtual void func() {}};
class Derived : public Base {public: void func() override {} // Correct
// void func(int) override {} // Error! Not overriding anything // void notVirtual() override {} // Error! Not virtual};Virtual Function Default Behavior
Section titled “Virtual Function Default Behavior”class Animal {public: virtual void speak() { std::cout << "Some animal sound\n"; }};
class Dog : public Animal {public: void speak() override { std::cout << "Woof!\n"; // Can still call base implementation Animal::speak(); }};Dynamic Cast with Polymorphism
Section titled “Dynamic Cast with Polymorphism”Use dynamic_cast for safe downcasting:
class Base {public: virtual ~Base() = default;};
class Derived : public Base {public: void specialMethod() { std::cout << "Special!\n"; }};
int main() { Base* base = new Derived();
// Unsafe cast (don't do this) // Derived* d = static_cast<Derived*>(base);
// Safe cast Derived* d = dynamic_cast<Derived*>(base); if (d) { d->specialMethod(); }
delete base;}Virtual Functions and Access Control
Section titled “Virtual Functions and Access Control”You can override with different access levels:
class Base {public: virtual void method() {}};
class Derived : public Base {private: void method() override {} // Now private in Derived!};
int main() { Derived d; // d.method(); // Error! method is private
Base* ptr = &d; ptr->method(); // OK! Calls Derived::method}Virtual Functions in Constructors/Destructors
Section titled “Virtual Functions in Constructors/Destructors”Be careful with virtual functions in constructors and destructors:
class Base {public: Base() { // Virtual functions don't work as expected here! // Calls Base::method(), not Derived::method() method(); }
virtual void method() { std::cout << "Base\n"; }
virtual ~Base() { // Similarly, destructor calls base version method(); }};
class Derived : public Base {public: void method() override { std::cout << "Derived\n"; }};Multiple Inheritance and Virtual Functions
Section titled “Multiple Inheritance and Virtual Functions”class Printable {public: virtual void print() const = 0; virtual ~Printable() {}};
class Logger {public: virtual void log() const = 0; virtual ~Logger() {}};
class Document : public Printable, public Logger {public: void print() const override { std::cout << "Printing\n"; }
void log() const override { std::cout << "Logging\n"; }};Complete Example: Payment System
Section titled “Complete Example: Payment System”#include <iostream>#include <vector>#include <memory>#include <string>
class PaymentMethod {protected: std::string holderName;
public: PaymentMethod(const std::string& holder) : holderName(holder) {}
virtual bool processPayment(double amount) = 0; virtual ~PaymentMethod() {}};
class CreditCard : public PaymentMethod {private: std::string cardNumber; double creditLimit;
public: CreditCard(const std::string& holder, const std::string& num, double limit) : PaymentMethod(holder), cardNumber(num), creditLimit(limit) {}
bool processPayment(double amount) override { if (amount > creditLimit) { std::cout << "Credit limit exceeded\n"; return false; } creditLimit -= amount; std::cout << "Credit card payment of $" << amount << " processed for " << holderName << "\n"; return true; }};
class PayPal : public PaymentMethod {private: std::string email;
public: PayPal(const std::string& holder, const std::string& em) : PaymentMethod(holder), email(em) {}
bool processPayment(double amount) override { std::cout << "PayPal payment of $" << amount << " processed for " << holderName << "\n"; return true; }};
class BankTransfer : public PaymentMethod {private: std::string accountNumber;
public: BankTransfer(const std::string& holder, const std::string& acc) : PaymentMethod(holder), accountNumber(acc) {}
bool processPayment(double amount) override { std::cout << "Bank transfer of $" << amount << " processed for " << holderName << "\n"; return true; }};
class ShoppingCart {private: std::vector<std::pair<std::string, double>> items; std::unique_ptr<PaymentMethod> paymentMethod;
public: void addItem(const std::string& name, double price) { items.emplace_back(name, price); }
void setPaymentMethod(std::unique_ptr<PaymentMethod> method) { paymentMethod = std::move(method); }
bool checkout() { double total = 0; for (const auto& item : items) { total += item.second; }
std::cout << "Total: $" << total << "\n";
if (paymentMethod) { return paymentMethod->processPayment(total); }
std::cout << "No payment method set\n"; return false; }};
int main() { ShoppingCart cart; cart.addItem("Laptop", 999.99); cart.addItem("Mouse", 29.99);
// Using different payment methods polymorphically cart.setPaymentMethod(std::make_unique<CreditCard>( "John Doe", "4111111111111111", 1000)); cart.checkout();
std::cout << "\n";
cart.setPaymentMethod(std::make_unique<PayPal>( "John Doe", "john@example.com")); cart.checkout();
return 0;}Key Takeaways
Section titled “Key Takeaways”- Polymorphism allows treating different types uniformly
- Virtual functions enable runtime polymorphism through the vtable
- Use
virtualto mark functions as overridable - Use
= 0for pure virtual functions (abstract methods) - Use
overridekeyword to catch errors - Always make base class destructors virtual when using polymorphism
- Abstract classes cannot be instantiated but define interfaces
Next Steps
Section titled “Next Steps”Now let’s learn about encapsulation and access specifiers.
Next Chapter: 12_encapsulation.md - Encapsulation and Access Specifiers