Virtual Functions and Runtime Polymorphism in C++

Last Updated : 19 Jan, 2026

A virtual function is a member function declared in a base class using the virtual keyword and overridden in a derived class. Virtual functions enable runtime polymorphism, where the function call is resolved at runtime based on the actual object type, not the pointer or reference type.

Runtime polymorphism is achieved through late binding, allowing different behaviors for the same function call in an inheritance hierarchy.

classa
Class Hierarchy

Important Notes About Virtual Functions

  • Function calls depend on the object type, not the pointer type.
  • Virtual function calls are not resolved in constructors.
  • Objects are constructed base → derived, so virtual dispatch is disabled during construction.
  • Virtual functions are resolved at runtime, not compile time.

Example Without Virtual Functions (Static Binding)

C++
#include <iostream>
using namespace std;

// Base class
class Shape {
public:
    Shape(int l, int w) : length(l), width(w) {}

    int get_Area() {
        cout << "This is call to parent class area\n";
        return 1;
    }

protected:
    int length, width;
};

// Derived class
class Square : public Shape {
public:
    Square(int l, int w) : Shape(l, w) {}

    int get_Area() {
        cout << "Square area: " << length * width << endl;
        return length * width;
    }
};

// Derived class
class Rectangle : public Shape {
public:
    Rectangle(int l, int w) : Shape(l, w) {}

    int get_Area() {
        cout << "Rectangle area: " << length * width << endl;
        return length * width;
    }
};

int main() {
    Shape* s;

    Square sq(5, 5);
    Rectangle rec(4, 5);

    s = &sq;
    s->get_Area();

    s = &rec;
    s->get_Area();

    return 0;
}

Output
This is call to parent class area
This is call to parent class area

Explanation:

  • "get_Area()" is not virtual in Shape.
  • Function calls are resolved at compile time (static binding).
  • Even though s points to derived objects, Shape::get_Area() is called.
  • This happens because the compiler binds the function based on the pointer type (Shape*).

Runtime Polymorphism Using Virtual Functions

To achieve runtime polymorphism, the base class function must be declared as virtual.

Example: C++ Program to Calculate the Area of Shapes using Virtual Function

C++
#include <iostream>
using namespace std;

// Base class
class Shape {
public:
    virtual void calculate() {
        cout << "Area of your Shape" << endl;
    }

    virtual ~Shape() {
        cout << "Shape Destructor Call\n";
    }
};

// Derived class
class Rectangle : public Shape {
public:
    int width, height;

    void calculate() {
        cout << "Enter Width of Rectangle: ";
        cin >> width;
        cout << "Enter Height of Rectangle: ";
        cin >> height;
        cout << "Area of Rectangle: " << width * height << endl;
    }

    ~Rectangle() {
        cout << "Rectangle Destructor Call\n";
    }
};

// Derived class
class Square : public Shape {
public:
    int side;

    void calculate() {
        cout << "Enter side of Square: ";
        cin >> side;
        cout << "Area of Square: " << side * side << endl;
    }

    ~Square() {
        cout << "Square Destructor Call\n";
    }
};

int main() {
    Shape* s;

    Rectangle r;
    s = &r;
    s->calculate();

    Square sq;
    s = &sq;
    s->calculate();

    return 0;
}

Output

Enter Width of Rectangle: 5
Enter Height of Rectangle: 10
Area of Rectangle: 50
Enter side of Square: 8
Area of Square: 64

Square Destructor Call
Shape Destructor Call
Rectangle Destructor Call
Shape Destructor Call

Explanation

  • "calculate()" is declared virtual in Shape.
  • Calls to "calculate()" are resolved at runtime.
  • The correct derived class function is invoked based on the actual object.
  • Virtual destructors ensure proper cleanup when deleting derived objects via base pointers.

Why Virtual Destructors Are Important

  • When deleting objects through a base class pointer, destructors must be virtual.
  • This prevents memory leaks and ensures derived destructors are executed.

Real-Life Example: Employee Management System

Consider employee management system where a base class Employee defines virtual functions such as raiseSalary() and promote(), which are overridden by derived classes like Manager or Engineer. This allows operations to be performed on a list of employees while the correct behavior is selected at runtime without knowing the specific employee type.

CPP
class Employee {
public:
    virtual void raiseSalary() {
        // common raise logic
    }

    virtual void promote() {
        // common promotion logic
    }
};

class Manager : public Employee {
public:
    void raiseSalary() {
        // manager-specific salary logic
    }

    void promote() {
        // manager-specific promotion logic
    }
};

void globalRaiseSalary(Employee* emp[], int n) {
    for (int i = 0; i < n; i++) {
        emp[i]->raiseSalary(); // Polymorphic call
    }
}

Explanation:

  • Employee* can point to any derived employee type.
  • raiseSalary() is resolved at runtime.
  • The correct function is executed without knowing the actual employee type.
  • This makes the system flexible, extensible, and maintainable.

Internal Working of Runtime Resolution

The compiler uses two internal mechanisms:

  • vtable: A table of function pointers, maintained per class. 
  • vptr: A pointer to vtable, maintained per object instance.
array

Runtime Process

1. Constructor initializes the vptr.

2. On a virtual call, the compiler:

  • Fetches the vptr
  • Accesses the vtable
  • Invokes the correct function address

Related Article:

vtable vs vptr

Comment