A virtual function is a function in a parent class that can be overridden by a child class, with the correct version being chosen automatically at runtime based on the actual object type. This is the core mechanism behind polymorphism in C++, one of the foundational principles of object-oriented programming. Virtual functions let you write code that works with a general type (like “Shape”) while automatically calling the right behavior for each specific type (like “Circle” or “Rectangle”) without knowing which one you’re dealing with ahead of time.
How Virtual Functions Enable Polymorphism
Imagine you have a parent class called Shape with a function called draw(). You also have child classes like Circle and Square, each with their own version of draw(). Without the virtual keyword, if you hold a Circle through a pointer to Shape, calling draw() runs the parent’s version. The compiler decides which function to call based on the pointer type, not the actual object. This is called static (or early) binding, because the decision is locked in at compile time.
By declaring draw() as virtual in the parent class, you tell the compiler: “Don’t decide which version to call right now. Figure it out when the program is actually running, based on what the object really is.” This is dynamic (or late) binding. The program looks up the correct function at runtime, so a Circle calls Circle::draw() and a Square calls Square::draw(), even when both are accessed through a generic Shape pointer. That automatic dispatch is what polymorphism means in practice.
Syntax in Base and Derived Classes
Declaring a virtual function is straightforward. You add the virtual keyword before the function declaration in the base class:
struct Base { virtual void foo() {} };
In the derived class, the function is automatically virtual because it matches the base class signature. You don’t need to repeat virtual. Instead, modern C++ recommends using the override keyword:
struct Derived : Base { void foo() override {} };
The override keyword doesn’t change behavior, but it’s a safety net. If you accidentally misspell the function name or get the parameter types slightly wrong, override causes a compilation error instead of silently creating a new, unrelated function. The C++ Core Guidelines recommend that every virtual function declaration should specify exactly one of virtual, override, or final: use virtual for the first appearance in the base class, and override for every derived class version.
Pure Virtual Functions and Abstract Classes
Sometimes a base class function has no meaningful default behavior. A generic Shape doesn’t know how to draw itself. In these cases, you can declare a pure virtual function by adding = 0 to the declaration:
virtual void draw() = 0;
A class with at least one pure virtual function becomes an abstract class. You cannot create objects of an abstract class directly. It exists solely as an interface, a contract that says “any child class must provide its own implementation of this function.” If a derived class doesn’t implement the pure virtual function, it also becomes abstract and can’t be instantiated. This pattern is useful for defining a common interface that multiple unrelated classes share, forcing each one to provide its own specific behavior.
Why Virtual Destructors Matter
One place where forgetting the virtual keyword causes real problems is destructors. When you delete a derived class object through a base class pointer, the program needs to call the derived class destructor to properly clean up its resources. If the base class destructor isn’t virtual, only the base class cleanup runs, potentially leaking memory or leaving resources open.
The general rule is simple: if your class has any virtual functions, make the destructor virtual too. If you’re designing a class meant to be inherited from, a virtual destructor is almost always the right call. The C++ FAQ from the Standard C++ Foundation puts it bluntly: either learn the rationale or just trust the advice and make base class destructors virtual.
Why Constructors Cannot Be Virtual
Unlike destructors, constructors can never be virtual in C++. The reason is practical: when an object is being created, the compiler needs to know the exact type so it can allocate the right amount of memory and initialize the object correctly. There’s no existing object yet to look up in a virtual table, so the concept doesn’t apply. Attempting to declare a virtual constructor produces a compiler error.
When you need constructor-like flexibility (choosing which type of object to create at runtime), the standard approach is the Factory Method pattern. You write a regular function that returns different derived class objects based on some input, giving you the effect of a “virtual constructor” without violating the language rules.
How Virtual Dispatch Works Under the Hood
When you declare a virtual function, the compiler creates a hidden lookup table called a vtable for each class that has virtual functions. Every object of that class contains a pointer to its class’s vtable. When your code calls a virtual function through a base class pointer, the program follows the object’s vtable pointer, looks up the correct function address, and jumps to it. This extra step is what makes runtime polymorphism possible.
This indirection is invisible to you as a programmer, but it has a small cost. A virtual function call involves reading the vtable pointer, loading the function address from the table, then jumping to that address. A non-virtual function call skips all of that because the compiler already knows the target at compile time.
Performance Cost of Virtual Functions
The raw overhead of a virtual call is modest. Virtual methods are only slightly more expensive than a regular non-inlined function call in terms of the call instruction itself. In most applications, this cost is negligible and not worth worrying about.
The real performance impact comes from two indirect effects. First, the compiler cannot inline a virtual function call because it doesn’t know which function will actually run. Inlining (inserting the function body directly at the call site) is one of the most effective compiler optimizations, and virtual calls block it entirely. For small functions called millions of times in a tight loop, losing inlining can matter significantly.
Second, virtual dispatch can cause cache misses and branch prediction failures. The CPU has to fetch the vtable, then fetch the function itself, and if your program uses many different derived types interchangeably, those function bodies may be scattered across memory. Each cache miss can cost roughly 30 to 60 CPU cycles, comparable to reading from the processor’s slowest cache level. In data-driven branching, the CPU tries to predict which function will be called next, but indirect calls through vtables are harder to predict than simple conditional branches.
That said, these costs only become meaningful in performance-critical inner loops processing millions of iterations. For the vast majority of code, virtual functions are the right tool for the job. If you later find a specific virtual call is a bottleneck through profiling, you can explore alternatives like templates or manual dispatch at that point. Prematurely avoiding virtual functions to save performance usually makes code harder to maintain without measurable benefit.

