A virtual function is a member function that is declared within a base class using the keyword virtual and is re-defined (Overridden) in the derived class. Virtual functions enable runtime polymorphism, calling the correct function via a base class pointer or reference.
- Virtual functions are mainly related to inheritance, allowing derived classes to provide their own implementation of base class functions.
- When a derived class object is deleted through a base class pointer, a virtual destructor in the base class ensures that both the derived and base class destructors are called, safely releasing all resources.
- The virtual destructor is declared using the keyword virtual in the base class.
- If a class has any virtual functions, its destructor should also be declared virtual. This is crucial for proper cleanup in polymorphic scenarios, especially when dealing with dynamic memory and inheritance.
C++
#include <iostream>
using namespace std;
class Shape
{
public:
// Virtual function
virtual void calculate()
{
cout << "Area of your Shape ";
}
// Virtual destructor
virtual ~Shape()
{
cout << "Shape Destructor called\n";
}
};
// Derived class: Rectangle
class Rectangle : public Shape
{
public:
int width, height, area;
void calculate() override
{
width = 5;
height = 10;
area = height * width;
cout << "Area of Rectangle: " << area << "\n";
}
~Rectangle()
{
cout << "Rectangle Destructor called\n";
}
};
// Derived class: Square
class Square : public Shape
{
public:
int side, area;
void calculate() override
{
side = 7;
area = side * side;
cout << "Area of Square: " << area << "\n";
}
~Square()
{
cout << "Square Destructor called\n";
}
};
int main()
{
Shape *S;
Rectangle r;
S = &r;
S->calculate();
Square sq;
S = &sq;
S->calculate();
return 0;
}
OutputArea of Rectangle: 50
Area of Square: 49
Square Destructor called
Shape Destructor called
Rectangle Destructor called
Shape Destructor called
Note: It is a recommended way to use override identifier to avoid mistakes while redefine virtual function inside the derived class.
Pure Virtual Function
A pure virtual function is a function in a base class with = 0 and no body, which must be overridden in derived classes. A class with such a function is called abstract and cannot be instantiated.
A pure virtual destructor is a destructor in a base class declared with = 0. It makes the class abstract and ensures that when you delete a derived class object through a base class pointer, the derived destructor runs first, followed by the base destructor, cleaning up everything properly.
C++
#include <iostream>
using namespace std;
class Base
{
public:
// Pure virtual function
virtual void display() = 0;
// Pure virtual destructor
virtual ~Base() = 0;
};
// Definition of pure virtual destructor
Base::~Base()
{
cout << "Base destructor called" << endl;
}
class Derived : public Base
{
public:
void display() override
{
cout << "Derived class display" << endl;
}
~Derived()
{
cout << "Derived destructor called" << endl;
}
};
int main()
{
Base *basePtr;
Derived derivedObj;
basePtr = &derivedObj;
basePtr->display();
return 0;
}
OutputDerived class display
Early Binding and Late Binding
When a function is called in the code, binding decides which function gets executed based on the context such as the type of object or the function signature. Binding happens at two levels:
- Early Binding: It happens when a function call is resolved during the program's compilation. This makes it faster because everything is decided early
- Late Binding: It happens with virtual functions where the exact function to call is decided at runtime, depending on the actual object type. This is slower because the program has to figure it out while running.
C++
#include <iostream>
using namespace std;
class base
{
public:
virtual void print()
{
cout << "print base "
"class\n";
}
void show()
{
cout << "show base class\n";
}
};
class derived : public base
{
public:
void print()
{
cout << "print derived class\n";
}
void show()
{
cout << "show derived class\n";
}
};
int main()
{
base *bptr;
derived d;
bptr = &d;
// Virtual function,binded at runtime
bptr->print();
// Non-virtual function,binded at compile time
bptr->show();
return 0;
}
Outputprint derived class
show base class
Explanation: In the above code, the print() function is declared with the virtual keyword so it will be bound at runtime and show() is non-virtual so it will be bound during compile time.
Note: If we have created a virtual function in the base class and it is being overridden in the derived class then we don’t need a virtual keyword in the derived class, functions are automatically considered virtual functions in the derived class.
Real-Life Example to Understand the Implementation of Virtual Function
Consider employee management software for an organization.
- We make a base class Employee with some common functions like raiseSalary() and promote(). These are declared virtual, so they can be overridden.
- Different types of employees (Manager, Engineer) inherit from Employee and override these functions with their own specific logic.
- In main(), we keep a list of Employee* pointers (pointing to both Manager and Engineer objects).
- When we loop through the list and call functions like raiseSalary() or promote(), polymorphism ensures that the correct version (Manager’s or Engineer’s) is called depending on the actual object type — even though we are using base class pointers.
C++
#include <iostream>
using namespace std;
class Employee
{
public:
virtual void raiseSalary()
{
cout << "Employee salary raised (general)" << endl;
}
virtual void promote()
{
cout << "Employee promoted (general)" << endl;
}
// virtual destructor
virtual ~Employee()
{
}
};
// Derived class: Manager
class Manager : public Employee
{
public:
void raiseSalary() override
{
cout << "Manager salary raised with incentives" << endl;
}
void promote() override
{
cout << "Manager promoted to Senior Manager" << endl;
}
};
// Derived class: Engineer
class Engineer : public Employee
{
public:
void raiseSalary() override
{
cout << "Engineer salary raised with bonus" << endl;
}
void promote() override
{
cout << "Engineer promoted to Senior Engineer" << endl;
}
};
int main()
{
// Create different employees
Manager m;
Engineer e;
// Array of base class pointers
Employee *employees[2] = {&m, &e};
// Raise salary for all employees
cout << "--- Raising Salaries ---" << endl;
for (int i = 0; i < 2; i++)
{
employees[i]->raiseSalary();
}
// Promote all employees
cout << "\n--- Promotions ---" << endl;
for (int i = 0; i < 2; i++)
{
employees[i]->promote();
}
return 0;
}
Output--- Raising Salaries ---
Manager salary raised with incentives
Engineer salary raised with bonus
--- Promotions ---
Manager promoted to Senior Manager
Engineer promoted to Senior Engineer
The compiler maintains two things to serve this purpose:
- vtable : A table of function pointers, maintained per class.
- vptr : A pointer to vtable, maintained per object instance (see this for an example).
The compiler adds additional code at two places to maintain and use vptr.
- Code in every constructor. This code sets the vptr of the object being created. This code sets vptr to point to the vtable of the class.
- Code with polymorphic function call (e.g. bp->show() in above code). Wherever a polymorphic call is made, the compiler inserts code to first look for vptr using a base class pointer or reference (In the above example, since the pointed or referred object is of a derived type, vptr of a derived class is accessed). Once vptr is fetched, vtable of derived class can be accessed. Using vtable, the address of the derived class function show() is accessed and called.
Rules for Virtual Functions
The rules for the virtual functions in C++ are as follows:
- Virtual functions are defined in the base class and can be overridden in derived classes (not mandatory; base version is used if not overridden).
- They must have the same prototype in base and derived classes.
- They are used through a base class pointer or reference to achieve runtime polymorphism.
- A class may have a virtual destructor in case of dynamic memory allocation, but never a virtual constructor.
- Virtual functions cannot be static, but they can be friend functions of another class.
Limitations of Virtual Functions
- Slower: The function call takes slightly longer due to the virtual mechanism and makes it more difficult for the compiler to optimize because it does not know exactly which function is going to be called at compile time.
- Difficult to Debug: In a complex system, virtual functions can make it a little more difficult to figure out where a function is being called from.
Explore
C++ Basics
Core Concepts
OOP in C++
Standard Template Library(STL)
Practice & Problems