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

C++ Tutorial · Polymorphism

Polymorphism lets one interface call different implementations. Master virtual functions, overrides, and when to use runtime polymorphism versus templates.

What you will learn

  • Declare virtual functions and overrides
  • Use base pointers/references to derived objects
  • Understand vtable and dynamic dispatch cost
  • Mark destructors virtual in base classes
  • Contrast with static polymorphism (templates)

Why this topic matters

Virtual functions are classic interview material—object slicing, vtables, and override/final keywords appear constantly.

Key terms & indexing

C++ polymorphism virtual function C++ override C++ vtable

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;
    }
};

Examples

1. Virtual override
class Animal {
public:
    virtual void sound() { cout << "Animal"; }
};
class Dog : public Animal {
public:
    void sound() override { cout << "Woof"; }
};
2. Base pointer call
Animal* p = new Dog();
p->sound();  // Woof
delete p;
3. Pure virtual shape
class Shape {
public:
    virtual double area() = 0;
};
4. Circle area
class Circle : public Shape {
    double r;
public:
    Circle(double x) : r(x) {}
    double area() override { return 3.14*r*r; }
};
5. Polymorphic vector
vector shapes;
shapes.push_back(new Circle(2));
for (auto* s : shapes) cout << s->area();
6. Virtual destructor
class Base {
public:
    virtual ~Base() {}
};
Base* p = new Derived();
delete p;  // safe cleanup
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;
}

Examples

1. Overload add(int)
int add(int a, int b) { return a + b; }
2. Overload add(double)
double add(double a, double b) { return a + b; }
3. Overload print(string)
void print(string s) { cout << s; }
4. Operator +
Complex operator+(const Complex& o) {
    return {real+o.real, imag+o.imag};
}
5. Template maximum
template
T maximum(T a, T b) { return (a>b)?a:b; }
6. Template Stack
Stack nums;
nums.push(10);
cout << nums.pop();

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;
};

Examples

1. Pure virtual interface
class IPrintable {
public:
    virtual void print() const = 0;
};
2. Implement interface
class Doc : public IPrintable {
public:
    void print() const override { cout << "Doc"; }
};
3. Abstract Shape
class Shape {
public:
    virtual double area() const = 0;
};
4. Square area
class Square : public Shape {
    double s;
public:
    double area() const override { return s*s; }
};
5. DB interface
class IDatabase {
public:
    virtual void connect() = 0;
};
6. MySQL connect
class MySQL : public IDatabase {
public:
    void connect() override { cout << "MySQL"; }
};
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).

Examples

1. Virtual in base
class Base {
public:
    virtual void f() { cout << "Base"; }
};
2. Override in derived
class Derived : public Base {
public:
    void f() override { cout << "Derived"; }
};
3. Pointer dispatch
Base* p = &derived;
p->f();  // Derived
4. Non-virtual call
p->nonVirtual();  // static binding
5. Object slicing
Base sliced = derived;
sliced.f();  // Base only
6. Virtual size cost
class V { virtual void f(); };
// object has hidden vptr
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.

Examples

1. CRTP call
template
class CRTPBase {
public:
    void go() { static_cast(this)->impl(); }
};
2. Visitor accept
class Circle : public Shape {
public:
    void accept(Visitor& v) override { v.visit(*this); }
};
3. std::variant
variant v = 42;
cout << get(v);
4. std::visit
visit([](auto& x){ cout << x; }, v);
5. Type erasure
TypeErasedPrinter p(3.14);
p.print();
6. Function object
function task = [](){ cout << "run"; };
task();
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

Examples

1. Virtual destructor
class GoodBase {
public:
    virtual ~GoodBase() {}
};
2. Use override
void f() override { cout << "Derived"; }
3. Avoid slicing
Derived d;
Base& ref = d;
ref.show();  // Derived
4. Smart pointer poly
unique_ptr p = make_unique();
5. Template instead
template
T add(T a,T b){ return a+b; }
6. Bad: no virtual dtor
class BadBase {};
BadBase* p = new Derived();
delete p;  // risky

Frequently asked questions

Why must the base destructor be virtual?

Deleting through a base pointer calls the correct derived destructor—otherwise resource leaks occur.

What is object slicing?

Copying derived object into base by value loses derived parts—use pointers/references for polymorphism.

What is pure virtual function?

Declared with = 0; makes class abstract; derived classes must implement it.