C++ Polymorphism Complete Guide
Object-Oriented Programming

C++ Polymorphism: Complete Guide with Examples

Master C++ polymorphism: runtime polymorphism (virtual functions) and compile-time polymorphism (function overloading, templates). Learn with practical examples and best practices for OOP design.

Runtime Poly

Virtual Functions

Compile-time

Templates & Overloading

Virtual Functions

Dynamic Dispatch

Abstract Classes

Pure Virtual

Introduction to Polymorphism

Polymorphism is a fundamental concept in Object-Oriented Programming (OOP) that allows objects of different types to be treated as objects of a common base type. The word "polymorphism" means "many forms" - it enables a single interface to represent different underlying forms (data types).

Why Use Polymorphism?
  • Code reusability and maintainability
  • Flexible and extensible design
  • Simplifies complex systems
  • Enables late binding (dynamic dispatch)
  • Supports interface-based programming
  • Facilitates code organization
Types of Polymorphism
  • Compile-time Polymorphism: Resolved during compilation
  • Runtime Polymorphism: Resolved during execution
  • Ad-hoc Polymorphism: Function/operator overloading
  • Parametric Polymorphism: Templates
  • Subtype Polymorphism: Inheritance + virtual functions
Polymorphism Hierarchy
Polymorphism
Compile-time Polymorphism
Function Overloading
Operator Overloading
Templates
Runtime Polymorphism
Virtual Functions
Function Overriding

C++ Polymorphism Comparison

The following table compares different types of polymorphism in C++ with their characteristics and use cases:

Type Mechanism When to Use Characteristics
Runtime Polymorphism Virtual Functions, Inheritance When behavior depends on object type at runtime Dynamic binding, uses vtable, slightly slower
Virtual Functions virtual keyword Base class defines interface, derived classes implement Enables function overriding, late binding
Abstract Classes Pure Virtual Functions Define interfaces without implementation Cannot instantiate, must be derived
Compile-time Polymorphism Function Overloading, Templates When behavior is known at compile time Static binding, no runtime overhead
Function Overloading Same name, different parameters Multiple functions with similar purpose Resolved at compile time
Operator Overloading Overload operators like +, -, *, etc. Make user-defined types act like built-in types Enhances readability, follows conventions
Templates Generic programming Write type-independent code Code reuse, type safety at compile time

1. Runtime Polymorphism (Virtual Functions)

Runtime polymorphism is achieved through virtual functions and inheritance. The actual function called is determined at runtime based on the object's type.

Basic Syntax
class Base {
public:
    virtual void show() { // Virtual function
        cout << "Base class show" << endl;
    }
};

class Derived : public Base {
public:
    void show() override { // Override virtual function
        cout << "Derived class show" << endl;
    }
};
Runtime Polymorphism Examples
#include <iostream>
#include <string>
#include <vector>
using namespace std;

// Example 1: Basic virtual function example
class Animal {
public:
    // Virtual function - can be overridden by derived classes
    virtual void makeSound() {
        cout << "Animal makes a sound" << endl;
    }
    
    // Virtual destructor - important for polymorphism
    virtual ~Animal() {
        cout << "Animal destroyed" << endl;
    }
};

class Dog : public Animal {
public:
    // Override virtual function
    void makeSound() override {
        cout << "Dog barks: Woof! Woof!" << endl;
    }
    
    ~Dog() override {
        cout << "Dog destroyed" << endl;
    }
};

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

class Cow : public Animal {
public:
    void makeSound() override {
        cout << "Cow moos: Moo! Moo!" << endl;
    }
};

// Example 2: Using polymorphism with pointers
void demonstratePolymorphism() {
    cout << "=== Example 1: Animal Sounds ===" << endl;
    
    // Base class pointer pointing to derived objects
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();
    Animal* animal3 = new Cow();
    Animal* animal4 = new Animal();  // Base class object
    
    // Runtime polymorphism - calls appropriate function
    animal1->makeSound();  // Calls Dog::makeSound()
    animal2->makeSound();  // Calls Cat::makeSound()
    animal3->makeSound();  // Calls Cow::makeSound()
    animal4->makeSound();  // Calls Animal::makeSound()
    
    // Clean up
    delete animal1;
    delete animal2;
    delete animal3;
    delete animal4;
}

// Example 3: Abstract class with pure virtual functions
class Shape {
public:
    // Pure virtual function - makes Shape abstract
    virtual double area() = 0;
    virtual double perimeter() = 0;
    virtual void display() {
        cout << "This is a shape" << endl;
    }
    
    virtual ~Shape() = default;
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14159 * radius * radius;
    }
    
    double perimeter() override {
        return 2 * 3.14159 * radius;
    }
    
    void display() override {
        cout << "Circle with radius " << radius << endl;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    double area() override {
        return width * height;
    }
    
    double perimeter() override {
        return 2 * (width + height);
    }
    
    void display() override {
        cout << "Rectangle " << width << " x " << height << endl;
    }
};

// Example 4: Polymorphism with containers
void shapeExample() {
    cout << "\n=== Example 2: Shapes Calculation ===" << endl;
    
    // Vector of Shape pointers (polymorphic container)
    vector<Shape*> shapes;
    
    shapes.push_back(new Circle(5.0));
    shapes.push_back(new Rectangle(4.0, 6.0));
    shapes.push_back(new Circle(3.5));
    shapes.push_back(new Rectangle(2.5, 8.0));
    
    // Process shapes polymorphically
    for (Shape* shape : shapes) {
        shape->display();
        cout << "Area: " << shape->area() << endl;
        cout << "Perimeter: " << shape->perimeter() << endl;
        cout << "---" << endl;
    }
    
    // Clean up
    for (Shape* shape : shapes) {
        delete shape;
    }
}

// Example 5: Employee hierarchy
class Employee {
protected:
    string name;
    double salary;
public:
    Employee(string n, double s) : name(n), salary(s) {}
    
    virtual double calculateBonus() {
        return salary * 0.10; // 10% bonus by default
    }
    
    virtual void displayInfo() {
        cout << "Employee: " << name << ", Salary: $" << salary << endl;
    }
    
    virtual ~Employee() {}
};

class Manager : public Employee {
private:
    double teamBonus;
public:
    Manager(string n, double s, double tb) : Employee(n, s), teamBonus(tb) {}
    
    double calculateBonus() override {
        return salary * 0.20 + teamBonus; // 20% + team bonus
    }
    
    void displayInfo() override {
        cout << "Manager: " << name << ", Salary: $" << salary 
             << ", Team Bonus: $" << teamBonus << endl;
    }
};

class Developer : public Employee {
private:
    int projectsCompleted;
public:
    Developer(string n, double s, int pc) : Employee(n, s), projectsCompleted(pc) {}
    
    double calculateBonus() override {
        return salary * 0.15 + (projectsCompleted * 1000); // 15% + project bonus
    }
    
    void displayInfo() override {
        cout << "Developer: " << name << ", Salary: $" << salary 
             << ", Projects: " << projectsCompleted << endl;
    }
};

void employeeExample() {
    cout << "\n=== Example 3: Employee Bonus Calculation ===" << endl;
    
    vector<Employee*> employees;
    
    employees.push_back(new Employee("John Doe", 50000));
    employees.push_back(new Manager("Jane Smith", 80000, 5000));
    employees.push_back(new Developer("Bob Johnson", 60000, 3));
    employees.push_back(new Manager("Alice Brown", 90000, 7000));
    
    for (Employee* emp : employees) {
        emp->displayInfo();
        cout << "Bonus: $" << emp->calculateBonus() << endl;
        cout << "---" << endl;
    }
    
    // Clean up
    for (Employee* emp : employees) {
        delete emp;
    }
}

int main() {
    demonstratePolymorphism();
    shapeExample();
    employeeExample();
    
    return 0;
}
Virtual Function Best Practices:
  • Always declare destructor as virtual in base classes
  • Use override keyword (C++11) for clarity
  • Prefer pure virtual functions for abstract classes
  • Use final keyword to prevent further overriding
  • Consider performance implications of virtual functions
Common Mistakes:
  • Forgetting virtual destructor in polymorphic base classes
  • Not making functions virtual when needed
  • Slicing objects when copying polymorphic types
  • Calling virtual functions from constructors/destructors
  • Overriding non-virtual functions

2. Compile-time Polymorphism

Compile-time polymorphism includes function overloading, operator overloading, and templates. The function to call is determined during compilation.

Function Overloading Syntax
void print(int x) {
    cout << "Integer: " << x << endl;
}

void print(double x) {
    cout << "Double: " << x << endl;
}

void print(string s) {
    cout << "String: " << s << endl;
}
Compile-time Polymorphism Examples
#include <iostream>
#include <string>
#include <cmath>
using namespace std;

// Example 1: Function Overloading
class MathOperations {
public:
    // Overloaded add functions
    int add(int a, int b) {
        cout << "Adding integers: ";
        return a + b;
    }
    
    double add(double a, double b) {
        cout << "Adding doubles: ";
        return a + b;
    }
    
    string add(string a, string b) {
        cout << "Concatenating strings: ";
        return a + b;
    }
    
    // Overloaded with different number of parameters
    int add(int a, int b, int c) {
        cout << "Adding three integers: ";
        return a + b + c;
    }
    
    // Overloaded with different parameter types
    double add(int a, double b) {
        cout << "Adding int and double: ";
        return a + b;
    }
};

// Example 2: Operator Overloading
class Complex {
private:
    double real, imag;
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    
    // Overload + operator
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
    
    // Overload - operator
    Complex operator-(const Complex& other) const {
        return Complex(real - other.real, imag - other.imag);
    }
    
    // Overload * operator
    Complex operator*(const Complex& other) const {
        return Complex(real * other.real - imag * other.imag,
                      real * other.imag + imag * other.real);
    }
    
    // Overload == operator
    bool operator==(const Complex& other) const {
        return (real == other.real) && (imag == other.imag);
    }
    
    // Overload << operator for output
    friend ostream& operator<<(ostream& os, const Complex& c) {
        os << c.real;
        if (c.imag >= 0) os << " + " << c.imag << "i";
        else os << " - " << -c.imag << "i";
        return os;
    }
    
    // Overload >> operator for input
    friend istream& operator>>(istream& is, Complex& c) {
        cout << "Enter real part: ";
        is >> c.real;
        cout << "Enter imaginary part: ";
        is >> c.imag;
        return is;
    }
    
    // Overload [] operator (unconventional for Complex, but for demo)
    double operator[](int index) const {
        if (index == 0) return real;
        else if (index == 1) return imag;
        else throw out_of_range("Complex index out of range");
    }
};

// Example 3: Template Functions (Generic Programming)
template
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// Template specialization for strings
template<>
string maximum(string a, string b) {
    return (a.length() > b.length()) ? a : b;
}

// Multiple template parameters
template
auto addMixed(T1 a, T2 b) -> decltype(a + b) {
    return a + b;
}

// Example 4: Template Classes
template
class Stack {
private:
    static const int MAX = 100;
    T elements[MAX];
    int top;
public:
    Stack() : top(-1) {}
    
    void push(T value) {
        if (top < MAX - 1) {
            elements[++top] = value;
        }
    }
    
    T pop() {
        if (top >= 0) {
            return elements[top--];
        }
        throw runtime_error("Stack underflow");
    }
    
    T peek() const {
        if (top >= 0) {
            return elements[top];
        }
        throw runtime_error("Stack is empty");
    }
    
    bool isEmpty() const {
        return top == -1;
    }
};

void demonstrateFunctionOverloading() {
    cout << "=== Example 1: Function Overloading ===" << endl;
    MathOperations math;
    
    cout << math.add(5, 3) << endl;
    cout << math.add(5.5, 3.2) << endl;
    cout << math.add("Hello, ", "World!") << endl;
    cout << math.add(1, 2, 3) << endl;
    cout << math.add(5, 3.14) << endl;
    cout << endl;
}

void demonstrateOperatorOverloading() {
    cout << "=== Example 2: Operator Overloading ===" << endl;
    Complex c1(3, 4);
    Complex c2(1, 2);
    
    cout << "c1 = " << c1 << endl;
    cout << "c2 = " << c2 << endl;
    
    Complex sum = c1 + c2;
    cout << "c1 + c2 = " << sum << endl;
    
    Complex diff = c1 - c2;
    cout << "c1 - c2 = " << diff << endl;
    
    Complex product = c1 * c2;
    cout << "c1 * c2 = " << product << endl;
    
    if (c1 == c1) {
        cout << "c1 equals itself" << endl;
    }
    
    cout << "c1[0] (real) = " << c1[0] << endl;
    cout << "c1[1] (imag) = " << c1[1] << endl;
    cout << endl;
}

void demonstrateTemplates() {
    cout << "=== Example 3: Template Functions ===" << endl;
    
    // Using template function with different types
    cout << "max(5, 10) = " << maximum(5, 10) << endl;
    cout << "max(3.14, 2.71) = " << maximum(3.14, 2.71) << endl;
    cout << "max('A', 'B') = " << maximum('A', 'B') << endl;
    cout << "max(\"short\", \"longer\") = " << maximum("short", "longer") << endl;
    
    cout << "addMixed(5, 3.14) = " << addMixed(5, 3.14) << endl;
    cout << "addMixed(2.5, 3) = " << addMixed(2.5, 3) << endl;
    cout << "addMixed(string(\"Result: \"), 100) = " << addMixed(string("Result: "), 100) << endl;
    cout << endl;
}

void demonstrateTemplateClasses() {
    cout << "=== Example 4: Template Classes ===" << endl;
    
    // Integer stack
    Stack intStack;
    intStack.push(10);
    intStack.push(20);
    intStack.push(30);
    
    cout << "Integer Stack: ";
    while (!intStack.isEmpty()) {
        cout << intStack.pop() << " ";
    }
    cout << endl;
    
    // String stack
    Stack stringStack;
    stringStack.push("World");
    stringStack.push("Hello");
    
    cout << "String Stack: ";
    while (!stringStack.isEmpty()) {
        cout << stringStack.pop() << " ";
    }
    cout << endl;
    
    // Double stack
    Stack doubleStack;
    doubleStack.push(3.14);
    doubleStack.push(2.71);
    
    cout << "Double Stack: ";
    while (!doubleStack.isEmpty()) {
        cout << doubleStack.pop() << " ";
    }
    cout << endl;
}

int main() {
    demonstrateFunctionOverloading();
    demonstrateOperatorOverloading();
    demonstrateTemplates();
    demonstrateTemplateClasses();
    
    return 0;
}

Runtime vs Compile-time Polymorphism

Use runtime polymorphism when behavior depends on object type at runtime. Use compile-time polymorphism when behavior is known at compile time and you want better performance.

3. Abstract Classes and Interfaces

Abstract classes cannot be instantiated and serve as base classes for other classes. They contain at least one pure virtual function.

Abstract Class Syntax
class AbstractClass {
public:
    virtual void pureVirtual() = 0; // Pure virtual function
    virtual ~AbstractClass() = default;
};
Abstract Classes and Interfaces Examples
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;

// Example 1: Pure Abstract Class (Interface)
class IPrintable {
public:
    virtual void print() const = 0;
    virtual ~IPrintable() = default;
};

class ICalculable {
public:
    virtual double calculate() const = 0;
    virtual ~ICalculable() = default;
};

// Class implementing multiple interfaces
class Document : public IPrintable, public ICalculable {
private:
    string content;
    int pages;
public:
    Document(string c, int p) : content(c), pages(p) {}
    
    void print() const override {
        cout << "Document: " << content 
             << " (Pages: " << pages << ")" << endl;
    }
    
    double calculate() const override {
        return pages * 0.05; // Cost per page
    }
};

// Example 2: Abstract Shape class
class Shape {
public:
    virtual double area() const = 0;      // Pure virtual
    virtual double perimeter() const = 0; // Pure virtual
    
    // Regular virtual function with implementation
    virtual void display() const {
        cout << "Shape information:" << endl;
    }
    
    virtual ~Shape() = default;
};

class Square : public Shape {
private:
    double side;
public:
    Square(double s) : side(s) {}
    
    double area() const override {
        return side * side;
    }
    
    double perimeter() const override {
        return 4 * side;
    }
    
    void display() const override {
        Shape::display();
        cout << "Type: Square" << endl;
        cout << "Side: " << side << endl;
        cout << "Area: " << area() << endl;
        cout << "Perimeter: " << perimeter() << endl;
    }
};

class Triangle : public Shape {
private:
    double base, height, side2, side3;
public:
    Triangle(double b, double h, double s2, double s3) 
        : base(b), height(h), side2(s2), side3(s3) {}
    
    double area() const override {
        return 0.5 * base * height;
    }
    
    double perimeter() const override {
        return base + side2 + side3;
    }
    
    void display() const override {
        Shape::display();
        cout << "Type: Triangle" << endl;
        cout << "Base: " << base << ", Height: " << height << endl;
        cout << "Sides: " << base << ", " << side2 << ", " << side3 << endl;
        cout << "Area: " << area() << endl;
        cout << "Perimeter: " << perimeter() << endl;
    }
};

// Example 3: Abstract Database Connection
class IDatabaseConnection {
public:
    virtual void connect() = 0;
    virtual void disconnect() = 0;
    virtual void executeQuery(const string& query) = 0;
    virtual ~IDatabaseConnection() = default;
};

class MySQLConnection : public IDatabaseConnection {
public:
    void connect() override {
        cout << "Connecting to MySQL database..." << endl;
    }
    
    void disconnect() override {
        cout << "Disconnecting from MySQL database..." << endl;
    }
    
    void executeQuery(const string& query) override {
        cout << "Executing MySQL query: " << query << endl;
    }
};

class PostgreSQLConnection : public IDatabaseConnection {
public:
    void connect() override {
        cout << "Connecting to PostgreSQL database..." << endl;
    }
    
    void disconnect() override {
        cout << "Disconnecting from PostgreSQL database..." << endl;
    }
    
    void executeQuery(const string& query) override {
        cout << "Executing PostgreSQL query: " << query << endl;
    }
};

// Example 4: Abstract Animal with Template Method Pattern
class Animal {
public:
    // Template method - defines algorithm skeleton
    void describe() const {
        cout << getSpecies() << ":" << endl;
        cout << "  Sound: " << makeSound() << endl;
        cout << "  Movement: " << move() << endl;
        cout << "  Diet: " << getDiet() << endl;
    }
    
    virtual ~Animal() = default;
    
protected:
    // Primitive operations to be overridden
    virtual string getSpecies() const = 0;
    virtual string makeSound() const = 0;
    virtual string move() const = 0;
    virtual string getDiet() const = 0;
};

class Lion : public Animal {
protected:
    string getSpecies() const override { return "Lion"; }
    string makeSound() const override { return "Roar"; }
    string move() const override { return "Walk/Run"; }
    string getDiet() const override { return "Carnivore"; }
};

class Eagle : public Animal {
protected:
    string getSpecies() const override { return "Eagle"; }
    string makeSound() const override { return "Screech"; }
    string move() const override { return "Fly"; }
    string getDiet() const override { return "Carnivore"; }
};

// Example 5: Using smart pointers with abstract classes
void demonstrateAbstractClasses() {
    cout << "=== Example 1: Abstract Shape Classes ===" << endl;
    
    vector<unique_ptr<Shape>> shapes;
    shapes.push_back(make_unique<Square>(5.0));
    shapes.push_back(make_unique<Triangle>(3.0, 4.0, 4.0, 5.0));
    
    for (const auto& shape : shapes) {
        shape->display();
        cout << "---" << endl;
    }
    
    cout << "\n=== Example 2: Database Connections ===" << endl;
    IDatabaseConnection* db1 = new MySQLConnection();
    IDatabaseConnection* db2 = new PostgreSQLConnection();
    
    db1->connect();
    db1->executeQuery("SELECT * FROM users");
    db1->disconnect();
    
    cout << endl;
    
    db2->connect();
    db2->executeQuery("SELECT * FROM products");
    db2->disconnect();
    
    delete db1;
    delete db2;
    
    cout << "\n=== Example 3: Template Method Pattern ===" << endl;
    Lion lion;
    Eagle eagle;
    
    lion.describe();
    cout << endl;
    eagle.describe();
    
    cout << "\n=== Example 4: Multiple Interfaces ===" << endl;
    Document doc("Annual Report", 50);
    IPrintable* printable = &doc;
    ICalculable* calculable = &doc;
    
    printable->print();
    cout << "Cost: $" << calculable->calculate() << endl;
}

int main() {
    demonstrateAbstractClasses();
    return 0;
}
Abstract Classes
  • Contains at least one pure virtual function
  • Cannot be instantiated
  • Can have data members and regular functions
  • Used to define interfaces
  • Derived classes must implement pure virtuals
Interfaces
  • All functions are pure virtual (C++ has no explicit interface)
  • No data members (ideally)
  • Defines a contract for implementing classes
  • Class can implement multiple interfaces
  • Enables polymorphism across unrelated classes

4. Virtual Tables and Dynamic Dispatch

Understanding how C++ implements runtime polymorphism through virtual tables (vtables) and virtual pointers (vptrs).

Virtual Table Mechanism
#include <iostream>
#include <iomanip>
using namespace std;

// Simplified demonstration of vtable concept
class Base {
public:
    virtual void func1() {
        cout << "Base::func1()" << endl;
    }
    
    virtual void func2() {
        cout << "Base::func2()" << endl;
    }
    
    void nonVirtual() {
        cout << "Base::nonVirtual()" << endl;
    }
    
    virtual ~Base() {}
};

class Derived : public Base {
public:
    void func1() override {
        cout << "Derived::func1()" << endl;
    }
    
    // func2() is inherited from Base
    // nonVirtual() is inherited from Base
};

// Demonstrate vtable behavior
void demonstrateVTables() {
    cout << "=== Virtual Table Demonstration ===" << endl;
    cout << endl;
    
    Base baseObj;
    Derived derivedObj;
    
    cout << "1. Direct calls:" << endl;
    baseObj.func1();
    derivedObj.func1();
    cout << endl;
    
    cout << "2. Using pointers (polymorphism):" << endl;
    Base* ptr;
    
    ptr = &baseObj;
    cout << "ptr points to Base: ";
    ptr->func1();  // Calls Base::func1()
    
    ptr = &derivedObj;
    cout << "ptr points to Derived: ";
    ptr->func1();  // Calls Derived::func1() - dynamic dispatch!
    cout << endl;
    
    cout << "3. Non-virtual function (static binding):" << endl;
    ptr = &derivedObj;
    ptr->nonVirtual();  // Always calls Base::nonVirtual()
    cout << endl;
    
    cout << "4. Slicing example:" << endl;
    Base sliced = derivedObj;  // Object slicing!
    sliced.func1();  // Calls Base::func1() - no polymorphism!
    cout << endl;
    
    cout << "5. Virtual destructor importance:" << endl;
    Base* polyPtr = new Derived();
    delete polyPtr;  // Correctly calls both destructors if virtual
}

// Size comparison
void demonstrateSizes() {
    cout << "\n=== Size Comparison ===" << endl;
    
    class NoVirtual {
        int x, y;
    public:
        void func() {}
    };
    
    class WithVirtual {
        int x, y;
    public:
        virtual void func() {}
        virtual ~WithVirtual() {}
    };
    
    class DerivedVirtual : public WithVirtual {
    public:
        void func() override {}
    };
    
    cout << "Size of NoVirtual: " << sizeof(NoVirtual) << " bytes" << endl;
    cout << "Size of WithVirtual: " << sizeof(WithVirtual) << " bytes" << endl;
    cout << "Size of DerivedVirtual: " << sizeof(DerivedVirtual) << " bytes" << endl;
    cout << endl;
    
    cout << "Note: Virtual classes have extra vptr (usually 8 bytes on 64-bit)" << endl;
}

// Performance consideration
void demonstratePerformance() {
    cout << "\n=== Performance Considerations ===" << endl;
    
    const int ITERATIONS = 1000000;
    
    class Fast {
    public:
        int process(int x) { return x * 2; }  // Non-virtual
    };
    
    class Slow {
    public:
        virtual int process(int x) { return x * 2; }  // Virtual
    };
    
    // Note: Actual benchmarking would require more sophisticated setup
    cout << "Virtual function calls have overhead because:" << endl;
    cout << "1. Extra indirection through vtable" << endl;
    cout << "2. Cannot be inlined by compiler" << endl;
    cout << "3. May cause cache misses" << endl;
    cout << endl;
    cout << "However, for most applications, this overhead is negligible" << endl;
    cout << "compared to the benefits of polymorphism." << endl;
}

// When to avoid virtual functions
void demonstrateWhenNotToUseVirtual() {
    cout << "\n=== When to Avoid Virtual Functions ===" << endl;
    
    cout << "1. In performance-critical sections:" << endl;
    cout << "   - Tight loops with many iterations" << endl;
    cout << "   - Real-time systems" << endl;
    cout << "   - High-frequency trading" << endl;
    cout << endl;
    
    cout << "2. When compile-time polymorphism suffices:" << endl;
    cout << "   - CRTP (Curiously Recurring Template Pattern)" << endl;
    cout << "   - Function overloading" << endl;
    cout << "   - Templates" << endl;
    cout << endl;
    
    cout << "3. In value-based semantics:" << endl;
    cout << "   - Small objects copied frequently" << endl;
    cout << "   - Objects stored in arrays/vectors" << endl;
}

int main() {
    demonstrateVTables();
    demonstrateSizes();
    demonstratePerformance();
    demonstrateWhenNotToUseVirtual();
    
    return 0;
}
VTable Implementation Details:
  • Each polymorphic class has a vtable (one per class)
  • Each object has a vptr pointing to its class's vtable
  • Virtual function calls go through vptr → vtable → function
  • Size overhead: vptr (usually 8 bytes on 64-bit systems)
  • Time overhead: One extra indirection per virtual call
  • Constructors initialize vptr, destructors may change it

5. Advanced Polymorphism Techniques

Advanced C++ polymorphism patterns including CRTP, type erasure, and visitor pattern.

Advanced Polymorphism Patterns
#include <iostream>
#include <vector>
#include <memory>
#include <typeinfo>
#include <any>
using namespace std;

// Pattern 1: CRTP (Curiously Recurring Template Pattern)
// Compile-time polymorphism without virtual functions
template
class CRTPBase {
public:
    void interface() {
        static_cast(this)->implementation();
    }
    
    void implementation() {
        cout << "Default implementation in CRTPBase" << endl;
    }
};

class CRTPDerived1 : public CRTPBase {
public:
    void implementation() {
        cout << "Custom implementation in CRTPDerived1" << endl;
    }
};

class CRTPDerived2 : public CRTPBase {
    // Uses default implementation
};

// Pattern 2: Type Erasure using std::function
class TypeErasedPrinter {
private:
    // Internal interface
    struct Concept {
        virtual ~Concept() = default;
        virtual void print() const = 0;
        virtual unique_ptr clone() const = 0;
    };
    
    // Model for specific types
    template
    struct Model : Concept {
        T value;
        Model(T v) : value(v) {}
        
        void print() const override {
            cout << "Value: " << value << endl;
        }
        
        unique_ptr clone() const override {
            return make_unique(*this);
        }
    };
    
    unique_ptr object;
    
public:
    template
    TypeErasedPrinter(T value) : object(make_unique>(value)) {}
    
    // Copy operations
    TypeErasedPrinter(const TypeErasedPrinter& other) 
        : object(other.object ? other.object->clone() : nullptr) {}
    
    TypeErasedPrinter& operator=(const TypeErasedPrinter& other) {
        if (this != &other) {
            object = other.object ? other.object->clone() : nullptr;
        }
        return *this;
    }
    
    void print() const {
        if (object) object->print();
    }
};

// Pattern 3: Visitor Pattern (Double Dispatch)
class Circle;
class Square;
class Triangle;

// Abstract Visitor
class ShapeVisitor {
public:
    virtual void visit(Circle& circle) = 0;
    virtual void visit(Square& square) = 0;
    virtual void visit(Triangle& triangle) = 0;
    virtual ~ShapeVisitor() = default;
};

// Abstract Element
class Shape {
public:
    virtual void accept(ShapeVisitor& visitor) = 0;
    virtual ~Shape() = default;
};

// Concrete Elements
class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double getRadius() const { return radius; }
    
    void accept(ShapeVisitor& visitor) override {
        visitor.visit(*this);
    }
};

class Square : public Shape {
private:
    double side;
public:
    Square(double s) : side(s) {}
    
    double getSide() const { return side; }
    
    void accept(ShapeVisitor& visitor) override {
        visitor.visit(*this);
    }
};

class Triangle : public Shape {
private:
    double base, height;
public:
    Triangle(double b, double h) : base(b), height(h) {}
    
    double getBase() const { return base; }
    double getHeight() const { return height; }
    
    void accept(ShapeVisitor& visitor) override {
        visitor.visit(*this);
    }
};

// Concrete Visitors
class AreaCalculator : public ShapeVisitor {
private:
    double totalArea = 0;
public:
    void visit(Circle& circle) override {
        double area = 3.14159 * circle.getRadius() * circle.getRadius();
        cout << "Circle area: " << area << endl;
        totalArea += area;
    }
    
    void visit(Square& square) override {
        double area = square.getSide() * square.getSide();
        cout << "Square area: " << area << endl;
        totalArea += area;
    }
    
    void visit(Triangle& triangle) override {
        double area = 0.5 * triangle.getBase() * triangle.getHeight();
        cout << "Triangle area: " << area << endl;
        totalArea += area;
    }
    
    double getTotalArea() const { return totalArea; }
};

class DescriptionVisitor : public ShapeVisitor {
public:
    void visit(Circle& circle) override {
        cout << "This is a circle with radius " << circle.getRadius() << endl;
    }
    
    void visit(Square& square) override {
        cout << "This is a square with side " << square.getSide() << endl;
    }
    
    void visit(Triangle& triangle) override {
        cout << "This is a triangle with base " << triangle.getBase() 
             << " and height " << triangle.getHeight() << endl;
    }
};

// Pattern 4: std::variant (C++17) as polymorphic type
#include <variant>
#include <string>

using ShapeVariant = variant;

void processVariant(const ShapeVariant& shape) {
    // Using std::visit with lambda
    visit([](auto& s) {
        using T = decay_t;
        if constexpr (is_same_v) {
            cout << "Processing Circle, radius: " << s.getRadius() << endl;
        } else if constexpr (is_same_v) {
            cout << "Processing Square, side: " << s.getSide() << endl;
        } else if constexpr (is_same_v) {
            cout << "Processing Triangle, base: " << s.getBase() 
                 << ", height: " << s.getHeight() << endl;
        }
    }, shape);
}

void demonstrateCRTP() {
    cout << "=== CRTP Pattern ===" << endl;
    CRTPDerived1 d1;
    CRTPDerived2 d2;
    
    d1.interface();  // Calls CRTPDerived1::implementation()
    d2.interface();  // Calls CRTPBase::implementation()
    cout << endl;
}

void demonstrateTypeErasure() {
    cout << "=== Type Erasure Pattern ===" << endl;
    
    vector items;
    items.emplace_back(42);           // int
    items.emplace_back(3.14159);      // double
    items.emplace_back(string("Hello")); // string
    items.emplace_back('A');          // char
    
    for (const auto& item : items) {
        item.print();
    }
    cout << endl;
}

void demonstrateVisitorPattern() {
    cout << "=== Visitor Pattern ===" << endl;
    
    vector> shapes;
    shapes.push_back(make_unique(5.0));
    shapes.push_back(make_unique(4.0));
    shapes.push_back(make_unique(3.0, 4.0));
    
    AreaCalculator areaCalc;
    DescriptionVisitor descVisitor;
    
    cout << "--- Description ---" << endl;
    for (const auto& shape : shapes) {
        shape->accept(descVisitor);
    }
    
    cout << "\n--- Area Calculation ---" << endl;
    for (const auto& shape : shapes) {
        shape->accept(areaCalc);
    }
    
    cout << "\nTotal area: " << areaCalc.getTotalArea() << endl;
    cout << endl;
}

void demonstrateVariant() {
    cout << "=== std::variant (C++17) ===" << endl;
    
    vector shapes;
    shapes.emplace_back(Circle(3.0));
    shapes.emplace_back(Square(4.0));
    shapes.emplace_back(Triangle(3.0, 4.0));
    
    for (const auto& shape : shapes) {
        processVariant(shape);
    }
    cout << endl;
}

int main() {
    demonstrateCRTP();
    demonstrateTypeErasure();
    demonstrateVisitorPattern();
    demonstrateVariant();
    
    return 0;
}
Choosing the Right Polymorphism Technique:
  • Virtual functions: When runtime behavior varies by type
  • CRTP: When you need compile-time polymorphism without vtable overhead
  • Type erasure: When you need to store heterogeneous types with common interface
  • Visitor pattern: When operations vary by both visitor and element types
  • std::variant: When types are known at compile time (C++17+)
  • Templates: When algorithms work with multiple types

6. Best Practices and Common Mistakes

Best Practices
  • Always declare destructor as virtual in polymorphic base classes
  • Use override keyword (C++11) for clarity
  • Prefer composition over inheritance when possible
  • Use abstract classes to define interfaces
  • Consider using smart pointers with polymorphic objects
  • Minimize virtual function overhead in performance-critical code
  • Document polymorphic behavior in class documentation
Common Mistakes
  • Object slicing when copying polymorphic objects
  • Forgetting to make destructor virtual
  • Calling virtual functions from constructors/destructors
  • Overusing inheritance instead of composition
  • Deep inheritance hierarchies (favor shallow)
  • Using polymorphism where templates would be better
  • Not using final when class shouldn't be derived
Good vs Bad Polymorphism Practices
#include <iostream>
#include <vector>
#include <memory>
using namespace std;

// BAD PRACTICE examples
class BadBase {
    // Missing virtual destructor!
public:
    virtual void doSomething() {
        cout << "Base implementation" << endl;
    }
    
    // Virtual function called in constructor - BAD!
    BadBase() {
        doSomething();  // Calls Base version, not Derived!
    }
};

class BadDerived : public BadBase {
public:
    void doSomething() override {
        cout << "Derived implementation" << endl;
    }
};

// GOOD PRACTICE examples
class GoodBase {
public:
    virtual void doSomething() {
        cout << "Base implementation" << endl;
    }
    
    // Virtual destructor - GOOD!
    virtual ~GoodBase() {
        cout << "GoodBase destroyed" << endl;
    }
    
    // No virtual calls in constructor
    GoodBase() {
        // Initialize base class members
    }
};

class GoodDerived : public GoodBase {
public:
    void doSomething() override {  // Using override - GOOD!
        cout << "Derived implementation" << endl;
    }
    
    ~GoodDerived() override {
        cout << "GoodDerived destroyed" << endl;
    }
};

// BAD: Deep inheritance hierarchy
class Animal {};
class Mammal : public Animal {};
class Dog : public Mammal {};
class Labrador : public Dog {};
class ChocolateLabrador : public Labrador {};  // Too deep!

// GOOD: Shallow hierarchy with composition
class AnimalGood {
    // Animal properties
};

class DogGood : public AnimalGood {
    // Dog-specific properties
    // Breed could be a member, not a derived class
};

// BAD: Using polymorphism where not needed
class MathProcessor {
public:
    virtual double process(double a, double b) = 0;  // Overkill
};

// GOOD: Using templates when appropriate
template
T add(T a, T b) {
    return a + b;
}

// BAD: Object slicing
void demonstrateSlicing() {
    cout << "=== Object Slicing Example ===" << endl;
    
    class Base {
    public:
        virtual void show() { cout << "Base" << endl; }
    };
    
    class Derived : public Base {
    public:
        void show() override { cout << "Derived" << endl; }
    };
    
    Derived derived;
    Base base = derived;  // SLICING! Derived part is lost
    base.show();  // Output: Base (not Derived!)
    cout << endl;
}

// GOOD: Using pointers/references to avoid slicing
void demonstrateNoSlicing() {
    cout << "=== Avoiding Object Slicing ===" << endl;
    
    class Base {
    public:
        virtual void show() { cout << "Base" << endl; }
        virtual ~Base() = default;
    };
    
    class Derived : public Base {
    public:
        void show() override { cout << "Derived" << endl; }
    };
    
    Derived derived;
    Base& ref = derived;  // Reference - no slicing
    Base* ptr = &derived; // Pointer - no slicing
    
    ref.show();  // Output: Derived
    ptr->show(); // Output: Derived
    cout << endl;
}

// GOOD: Using smart pointers with polymorphism
void demonstrateSmartPointers() {
    cout << "=== Smart Pointers with Polymorphism ===" << endl;
    
    class Resource {
    public:
        virtual void use() = 0;
        virtual ~Resource() = default;
    };
    
    class MemoryResource : public Resource {
    public:
        void use() override { cout << "Using memory" << endl; }
        ~MemoryResource() { cout << "MemoryResource cleaned up" << endl; }
    };
    
    class FileResource : public Resource {
    public:
        void use() override { cout << "Using file" << endl; }
        ~FileResource() { cout << "FileResource cleaned up" << endl; }
    };
    
    vector> resources;
    resources.push_back(make_unique());
    resources.push_back(make_unique());
    
    for (const auto& resource : resources) {
        resource->use();
    }
    // Resources automatically cleaned up when vector goes out of scope
    cout << endl;
}

int main() {
    // Bad examples
    cout << "=== BAD PRACTICES ===" << endl;
    BadBase* badPtr = new BadDerived();
    delete badPtr;  // Potential undefined behavior - no virtual destructor!
    
    demonstrateSlicing();
    
    // Good examples  
    cout << "\n=== GOOD PRACTICES ===" << endl;
    GoodBase* goodPtr = new GoodDerived();
    delete goodPtr;  // Correct - calls both destructors
    
    demonstrateNoSlicing();
    demonstrateSmartPointers();
    
    // Template example
    cout << "Template add function:" << endl;
    cout << "add(5, 3) = " << add(5, 3) << endl;
    cout << "add(2.5, 3.7) = " << add(2.5, 3.7) << endl;
    cout << "add(string(\"Hello, \"), string(\"World!\")) = " 
         << add(string("Hello, "), string("World!")) << endl;
    
    return 0;
}