Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

3.3 Polymorphism — Virtual Functions, Pure Virtual & Abstract Classes

Lesson 15 of 22 in the free Object Oriented Programming with C++ notes on Siksha Sarovar, written by Rohit Jangra.

3.3 Polymorphism — Virtual Functions, Pure Virtual & Abstract Classes

What is Polymorphism?

Polymorphism (Greek: "many forms") means the same interface behaves differently for different types. In OOP, the same function call may invoke different code depending on which object receives it.

Types of polymorphism

TypeWhen resolvedMechanism
Compile-time (static)At compileFunction overloading, operator overloading, templates
Runtime (dynamic)At runtimeVirtual functions
                  Polymorphism
                       │
        ┌──────────────┴───────────────┐
        │                              │
   Compile-Time                   Runtime
   (Static / Early Binding)      (Dynamic / Late Binding)
        │                              │
   ┌────┴────┐                     Virtual
   │         │                     Functions
Function   Operator
Overloading Overloading
   │
Templates

---

Compile-Time Polymorphism (Static Binding / Early Binding)

The compiler decides at compile time which function to call.

Function overloading

class Math {
public:
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; }
};

Math m;
m.add(1, 2);       // compiler picks int version
m.add(1.5, 2.5);   // compiler picks double version

Operator overloading (covered in next lesson)

Complex c = c1 + c2;   // operator+ resolved at compile time

Templates (Unit IV)

template<typename T>
T max(T a, T b) { return (a > b) ? a : b; }

max(3, 5);        // T = int
max(2.5, 3.5);    // T = double

---

Runtime Polymorphism (Dynamic Binding / Late Binding)

The compiler does not know at compile time which function to call. The decision is made at runtime based on the actual object type.

This is achieved through virtual functions and base class pointers/references.

class Shape {
public:
    virtual void draw() {              // virtual function
        cout << "Drawing a shape\n";
    }
};

class Circle : public Shape {
public:
    void draw() override {              // overrides
        cout << "Drawing a circle\n";
    }
};

class Square : public Shape {
public:
    void draw() override {
        cout << "Drawing a square\n";
    }
};

int main() {
    Shape* shapes[] = {new Circle(), new Square(), new Shape()};
    for (int i = 0; i < 3; i++) {
        shapes[i]->draw();    // runtime polymorphism — actual type decides
    }
    return 0;
}
// Output:
// Drawing a circle
// Drawing a square
// Drawing a shape
Without virtual, all three calls would print "Drawing a shape" — because the compiler resolves based on the pointer type (Shape*).

---

Early Binding vs Late Binding — exam table

AspectEarly Binding (Static)Late Binding (Dynamic)
When resolvedAt compile timeAt runtime
Achieved byRegular functions, overloadingVirtual functions
MechanismFunction name → function callvtable lookup
SpeedFaster (direct call)Slightly slower (one indirection)
Memory overheadNonevtable per class + vptr per object
FlexibilityLessMore
UseMost function callsPolymorphic hierarchies

---

Virtual Functions — How They Work

The Problem They Solve

Without virtual:

class Animal {
public:
    void speak() { cout << "Generic sound\n"; }
};

class Dog : public Animal {
public:
    void speak() { cout << "Bark!\n"; }
};

int main() {
    Animal* a = new Dog();
    a->speak();        // "Generic sound" — based on pointer type
    return 0;
}

With virtual:

class Animal {
public:
    virtual void speak() { cout << "Generic sound\n"; }
};

class Dog : public Animal {
public:
    void speak() override { cout << "Bark!\n"; }
};

int main() {
    Animal* a = new Dog();
    a->speak();        // "Bark!" — based on object type
    return 0;
}

---

vtable and vptr (the implementation)

C++ implements virtual functions via:

  • vtable (virtual table) — a per-class table of function pointers
  • vptr (virtual pointer) — a hidden pointer in each object pointing to its class's vtable
Class A:                       Object of class A:
┌────────────┐                ┌──────────┐
│ A's vtable │ ◄──────────────┤ vptr     │
│  ┌──────┐  │                ├──────────┤
│  │ f1 →│ A::f1()            │ data ... │
│  │ f2 →│ A::f2()            └──────────┘
│  └──────┘  │
└────────────┘

Class B : A (overrides f2):    Object of class B:
┌────────────┐                ┌──────────┐
│ B's vtable │ ◄──────────────┤ vptr     │
│  ┌──────┐  │                ├──────────┤
│  │ f1 →│ A::f1() (inherited)│ data ... │
│  │ f2 →│ B::f2() (override) └──────────┘
│  └──────┘  │
└────────────┘

When you call a->f2():

  1. Compiler emits: "look up function via vptr"
  2. At runtime: vptr points to the actual object's class's vtable
  3. The correct function pointer is loaded and called

Cost: one extra pointer dereference per virtual call. Modern CPUs handle this efficiently via branch prediction.

---

virtual Keyword Rules

RuleDetail
Declared in baseUse virtual keyword
Override in derivedvirtual is implied; override (C++11) is recommended
Once virtual, always virtualDerived overrides are virtual whether you write the keyword or not
Signature must matchSame name, same parameters, same return type (or covariant — special rule)
Cannot be staticStatic + virtual makes no sense
Cannot be inline(Usually — they're called via pointer, not inlined)
Constructors cannot be virtualObject type unknown at construction
Destructors should be virtualIf class is meant for inheritance

override keyword (C++11)

Always use override when overriding:

class Base {
public:
    virtual void f(int) {}
};

class Derived : public Base {
public:
    void f(int) override {}     // OK — actually overrides
    void g(int) override {}     // ERROR — Base doesn't have g
    void f(double) override {}  // ERROR — Base::f takes int, not double
};

override makes the compiler verify that you actually override.

---

Pure Virtual Functions

A pure virtual function is a virtual function declared with = 0 in the base class. It has no body in the base — derived classes must override it.

class Shape {
public:
    virtual double area() = 0;        // pure virtual
    virtual void draw() = 0;          // pure virtual
};

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() override { return 3.14 * radius * radius; }
    void draw() override { cout << "Drawing circle\n"; }
};

Syntax: virtual <return-type> <name>(<params>) = 0;

The = 0 is the pure specifier — not "returns 0", just a syntactic marker.

---

Abstract Classes

A class containing at least one pure virtual function is an abstract class.

Properties

  • Cannot be instantiatedShape s; is an error
  • Can be inherited from
  • Derived classes must override all pure virtuals (or remain abstract)
  • Can have data members, constructors, destructors, regular member functions
Shape s;                     // ERROR — Shape is abstract
Shape* p = new Circle(5);    // OK — pointer to derived
Circle c(5);                 // OK — Circle is concrete (overrides both)

When to use abstract classes

  • Define an interface that derived classes must implement
  • Force a contract — "every Shape must have area() and draw()"
  • Polymorphic operations on objects of common type
  • Template Method pattern — base class outlines algorithm, derived fills in steps

Pure virtual function with a body

Surprisingly, a pure virtual function can have a body:

class Animal {
public:
    virtual void speak() = 0;
};

void Animal::speak() {        // pure virtual CAN have a body
    cout << "Generic sound\n";
}

class Dog : public Animal {
public:
    void speak() override {
        Animal::speak();      // call the "default" implementation
        cout << "Bark!\n";
    }
};

Use case: provide a default that derived classes can extend.

---

Concrete vs Abstract Classes

AspectConcrete ClassAbstract Class
Pure virtuals?NoneAt least one
Instantiable?YesNo
UseCreate objectsDefine interface / contract
Member functionsAll definedSome pure virtual

---

Interface vs Abstract Class

C++ doesn't have a distinct "interface" keyword (like Java). An interface is conventionally a class with only pure virtual functions and no data members:

class Drawable {                       // interface (by convention)
public:
    virtual void draw() = 0;
    virtual ~Drawable() = default;     // virtual dtor (important!)
};

class Loggable {
public:
    virtual void log() const = 0;
    virtual ~Loggable() = default;
};

class Shape : public Drawable, public Loggable {
public:
    void draw() override { /* ... */ }
    void log() const override { /* ... */ }
};

Multiple inheritance from interfaces is common and safe (no diamond problem since no data).

---

Pointer to Derived Class Objects

The polymorphism mechanism requires a base class pointer or reference to a derived class object:

Shape* p = new Circle(5);   // base pointer → derived object

Slicing (the gotcha)

If you pass a derived object by value to a base parameter, the derived parts are sliced off:

void printArea(Shape s) {    // BY VALUE — slicing!
    cout << s.area();
}

Circle c(5);
printArea(c);     // c is copied to Shape s — Circle parts lost
                   // s.area() may not behave as expected

Fix: pass by reference or pointer:

void printArea(const Shape& s) {     // OK — reference, no slicing
    cout << s.area();
}

---

Complete Example — Polymorphism

class Employee {
protected:
    string name;
    double baseSalary;

public:
    Employee(string n, double s) : name(n), baseSalary(s) {}

    virtual double calculateSalary() = 0;   // pure virtual

    virtual void display() {                 // virtual, has default impl
        cout << name << ": ₹" << calculateSalary() << endl;
    }

    virtual ~Employee() = default;           // virtual destructor
};

class Manager : public Employee {
private:
    double bonus;
public:
    Manager(string n, double s, double b) : Employee(n, s), bonus(b) {}

    double calculateSalary() override {
        return baseSalary + bonus;
    }
};

class Engineer : public Employee {
private:
    double overtime;
public:
    Engineer(string n, double s, double o) : Employee(n, s), overtime(o) {}

    double calculateSalary() override {
        return baseSalary + (overtime * 500);
    }
};

int main() {
    vector<Employee*> staff = {
        new Manager("Rohit", 100000, 25000),
        new Engineer("Priya", 80000, 20),
        new Manager("Amit", 120000, 30000)
    };

    for (Employee* e : staff) {
        e->display();   // runtime polymorphism — correct salary calculated
    }

    for (Employee* e : staff) delete e;
    return 0;
}

---

Study deep

  1. Virtual function = function pointer with a vptr. Conceptually, a virtual function is just a function pointer in a table, looked up at runtime. The cost: one extra memory access. The benefit: polymorphism.
  1. final can speed up code. Marking a class or function final (C++11) tells the compiler no further overriding can happen — it can devirtualize the call (skip the vtable lookup).
  1. Abstract classes are the C++ way of doing interfaces. Java's interface keyword maps to C++ classes with only pure virtuals. Use them for contracts, dependency injection, plugin systems.
  1. Slicing is a real bug source. Passing polymorphic objects by value silently loses derived behaviour. Always pass by reference (const T& or T&) or pointer for polymorphic interfaces.
  1. CRTP — Curiously Recurring Template Pattern. Modern C++ achieves static polymorphism with templates:
template<typename Derived>
class Base { void interface() { static_cast<Derived*>(this)->impl(); } };

class D : public Base<D> { void impl() { /* ... */ } };

Zero runtime cost, but loses runtime polymorphism. Used in performance-critical libraries.

Key Terms — Lesson 3.3

The terms below define the polymorphism vocabulary — every PYQ on virtual functions, abstract classes, or polymorphism types expects fluent use.

Polymorphism — The fourth OOP pillar: the same operation behaves differently for different types. Greek for "many forms." Two forms in C++: compile-time polymorphism (function overloading, operator overloading, templates) and runtime polymorphism (virtual functions, dynamic binding).

Compile-Time Polymorphism / Static Polymorphism / Early Binding — Polymorphism resolved at compile time based on the static type / signature. Implemented via function overloading, operator overloading, and templates. Fast (zero runtime cost), but the variation must be known at compile time.

Runtime Polymorphism / Dynamic Polymorphism / Late Binding — Polymorphism resolved at runtime based on the dynamic (actual) type of the object. Implemented via virtual functions in C++. Slower (one indirection per call) but allows flexibility unknown at compile time.

Virtual Function — A member function declared with the virtual keyword in the base class. When called through a base-class pointer or reference, the derived-class override is invoked — even though the static type is base. The mechanism by which C++ implements runtime polymorphism.

Function Override — When a derived class defines a function with the same signature as a base virtual function, replacing the base's behaviour for objects of the derived type. The override keyword (C++11) makes intent explicit and lets the compiler catch mismatches.

Function Overloading vs Function Overriding — Two unrelated concepts that share two letters. Overloading is same name, different signatures, same scope — compile-time resolved, no inheritance needed. Overriding is same signature, different scope (base vs derived) — runtime resolved via virtual functions.

Pure Virtual Function — A virtual function declared with = 0 at the end: virtual void area() = 0;. Has no implementation in the base class (though one can be provided separately). A class with one or more pure virtuals is an abstract class. Derived classes must override every pure virtual or themselves become abstract.

Abstract Class — A class with at least one pure virtual function. Abstract classes cannot be instantiated directly; they exist to define a contract that derived classes must implement. The C++ equivalent of Java's interface.

Concrete Class — A class with no pure virtual functions (all virtuals are implemented). Concrete classes can be instantiated.

Interface (in C++) — Not a language keyword; modelled as a class with only pure virtual functions (and a virtual destructor). Used for dependency injection, plugin systems, and strategy patterns.

Virtual Destructor — A destructor declared virtual in a base class: virtual ~Base() {}. Mandatory for any class intended for polymorphic use (objects of derived classes destroyed via base-class pointers). Without a virtual destructor, only the base destructor runs and derived resources leak.

vtable (Virtual Table) — A per-class table of function pointers built by the compiler for every class with virtual functions. Each entry points to the most-derived implementation of one virtual function. The vtable is shared across all objects of the class.

vptr (Virtual Pointer / Virtual Table Pointer) — A hidden pointer inside every object of a polymorphic class that points to the class's vtable. The vptr is initialised in the constructor; virtual calls go through vptr → vtable → function pointer. Costs one extra pointer per object.

Dynamic Binding / Late Binding — The process by which a virtual function call is resolved at runtime by looking up vptr → vtable → function pointer. The slower but more flexible counterpart to static (early) binding, which resolves the call at compile time.

Object Slicing — When a derived object is assigned or passed by value to a base type, the derived-specific part is sliced off — only the base sub-object remains. Slicing silently destroys polymorphism: virtual calls on the resulting object dispatch to the base versions. Avoid by passing/storing polymorphic objects via reference or pointer.

Base-Class Pointer / Reference — A pointer or reference whose static type is the base class, but whose actual target can be of any derived type. The vehicle by which polymorphism is invoked — without base-class pointers or references, all calls are statically resolved.

Upcasting — Converting a derived pointer/reference to a base pointer/reference: Base* b = new Derived();. Always safe, happens implicitly, and is the prerequisite for runtime polymorphism.

Downcasting — Converting a base pointer/reference back to a derived pointer/reference. Not generally safe — the target must actually be of the derived type. Use dynamic_cast for safe downcasting (returns nullptr on type mismatch); static_cast is unsafe and assumes the cast is valid.

RTTI (Run-Time Type Information) — Compiler-generated metadata that lets a program inspect the actual type of an object at runtime. Required for dynamic_cast and typeid. RTTI is on by default; can be disabled (-fno-rtti) for performance / code-size reasons.

typeid Operator — Returns a std::type_info object describing the type of its argument. Combined with comparing type_info::name(), allows runtime type queries. Lower-level than dynamic_cast.

Devirtualisation — A compiler optimisation that eliminates the virtual call when the actual type can be determined at compile time — typically when the object is a local with no virtual subobject manipulation, or when the class is marked final. Can recover the cost of virtual dispatch.

Function Pointer (as Manual Polymorphism) — Before OOP, C used function pointers for polymorphism — a struct contained a function pointer that pointed to one of several implementations. C++ virtual functions are essentially structured, type-safe function pointer tables.

Subtype Polymorphism — The academic name for what C++ implements via virtual functions — a value of a subtype can be used wherever a supertype value is expected. Distinguish from parametric polymorphism (templates / generics) and ad-hoc polymorphism (overloading).

Parametric Polymorphism — Polymorphism via templates / generics — code that works for any type parameter. std::vector<T> is parametric polymorphism — the implementation is the same regardless of T.

Ad-Hoc Polymorphism — Polymorphism via overloading — the same name is given multiple unrelated implementations for different argument types. abs(int), abs(double), abs(complex) are ad-hoc polymorphism.

Strategy Pattern — A design pattern that uses polymorphism (abstract class + multiple concrete strategies) to vary behaviour at runtime. The client holds a pointer to the abstract strategy and calls its methods; concrete strategy classes implement the variations. Classic textbook example: different sorting algorithms behind the same interface.

---

PYQ pattern (very common): "What is polymorphism? Differentiate compile-time and runtime polymorphism." — Define; show diagram (static = overloading/templates; dynamic = virtual functions); table 5-6 differences (when resolved, mechanism, speed, memory).
PYQ pattern: "What is a virtual function? Explain with example." — Definition; example with Shape and Circle; show output difference with vs without virtual; mention vtable / vptr.
PYQ pattern: "What is pure virtual function? What is an abstract class?" — Define pure virtual (= 0); abstract class = has pure virtual; cannot instantiate; must override in derived; show Shape example.