Virtual Function
Virtual functions seem like a simple concept when we look at the syntax. We just add the virtual keyword to a function, and things start working differently.
But the interesting part is not how to use a virtual function. It is understanding why we need it in the first place and what makes it such a big deal in C++.
Let’s start with a simple example.
#include <iostream>
class Base
{
public:
void fun()
//virtual void fun()
{std::cout<<"Base Class\n";}
};
class Derived : public Base
{
public:
void fun()
{std::cout<<"Derived Class\n";}
};
int main()
{
Base b;
Derived d;
b.fun();
d.fun();
std::cout<<"now \n";
Base* p;
p = new Derived();
p->fun();
delete p;
return 0;
}The first two function calls are straightforward. The object b calls fun() from the Base class, while the object d calls fun() from the Derived class.
Nothing surprising here. The compiler knows the type of each object and can determine which function to call. This is called early/static binding.
But look at what happens next.
We create a pointer p of type Base*, but make it point to an object of type Derived. When we call p->fun(), the function from the Base class is called.
Why?
The pointer refers to a Derived object, so why doesn't the program call Derived::fun()?
The reason is that fun() is not a virtual function. The compiler resolves this call using the static type of the pointer, which is Base*.
Now uncomment the virtual keyword in the Base class and run the program again. The last function call now invokes Derived::fun().
That’s the magic of virtual functions.
But wait. Why should we want a function call to be decided at runtime when the compiler can already determine function calls at compile time? And if the compiler has already converted our program into machine instructions, how does the program figure out which function to call while it is running?
To understand why this matters, let’s look at a more practical example.
#include <iostream>
#include <memory>
using namespace std;
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing Circle\n";
}
};
class Rectangle : public Shape {
public:
void draw() override {
cout << "Drawing Rectangle\n";
}
};
void render(Shape& s) {
s.draw();
}
int main() {
int choice;
cin >> choice;
unique_ptr<Shape> shape;
if (choice == 1)
shape = make_unique<Circle>();
else
shape = make_unique<Rectangle>();
render(*shape);
}Imagine we are building a graphics application that can draw different shapes. The user might choose a circle or a rectangle, and each shape has its own implementation of draw().
Get Vivekgaurav’s stories in your inbox
Join Medium for free to get updates from this writer.
In the above code, the object is created based on the user’s choice at runtime. However, notice something interesting: the render() function doesn't care whether it receives a circle or a rectangle.
It simply accepts a reference to Shape and calls s.draw().
So how does the program know whether to draw a circle or a rectangle?
The answer is runtime polymorphism. Because draw() is virtual, the call can be dispatched to the appropriate implementation based on the actual object that s refers to.
Without virtual functions, calling s.draw() through a Shape& would not provide this behavior. The call would resolve to the base-class implementation, if one existed, rather than dynamically selecting the derived implementation.
This is where virtual functions become useful. We can write code that works with a common interface, even when the actual types of the objects are different and may not be known until runtime.
But that brings us back to the original question: how does the program select the correct function?
A common implementation technique used by C++ compilers involves something called a virtual table, or vtable.
Conceptually, we can imagine the tables looking something like this:
Base vtable
fun() → Base::fun()Derived vtable
fun() → Derived::fun()When an object is created, a common implementation uses a hidden pointer within the object, usually called a virtual pointer or vptr. This pointer helps the program locate the virtual table associated with the object’s dynamic type.
When we call a virtual function through a base-class pointer or reference, the program uses this mechanism to select the appropriate function.
In our earlier example, even though p is of type Base*, it points to a Derived object. The virtual dispatch mechanism therefore selects Derived::fun() instead of Base::fun().
Of course, this is a simplified explanation of how virtual functions are commonly implemented. The C++ standard specifies the behavior of virtual functions, but it doesn’t require compilers to use vtables and vptrs specifically.




