Performance Impact of Virtual Pointers in C++
Introducing a virtual pointer (vptr) to an otherwise
standard-layout C++ struct fundamentally changes how the
compiler handles the type, resulting in both direct and indirect
performance penalties. Adding a virtual member function or inheriting
virtually forces the compiler to insert a hidden pointer to a virtual
method table (vtable). This addition increases the object's
memory footprint, degrades CPU cache locality, introduces indirection
overhead during function calls, prevents compiler inlining, and strips
the type of its standard-layout and trivial-copy guarantees.
Increased Memory Footprint and Alignment Padding
The most immediate physical consequence of introducing a
vptr is an increase in object size (sizeof).
On 64-bit architectures, a pointer occupies 8 bytes (4 bytes on 32-bit
systems).
Because the vptr requires pointer-sized alignment,
adding it can also introduce padding bytes depending on the alignment
requirements of the other members. For example, a struct containing only
two 4-byte integers changes from 8 bytes to 16 bytes on a 64-bit
architecture. This doubling of size directly reduces the number of
instances that can fit into a single 64-byte L1 CPU cache line, cutting
cache capacity and increasing cache misses when iterating over arrays or
contiguous vectors of these objects.
Dynamic Dispatch and Loss of Inlining
When a function call is routed through a vptr, the
compiler can no longer resolve the target address at compile time.
Instead, it must generate machine code to perform dynamic dispatch:
- Fetch the object's
vptrfrom memory. - Read the function pointer from the
vtableat a fixed offset. - Perform an indirect jump (
CALL) to that address.
This indirection stalls the CPU pipeline if the target address is not
in the branch target buffer (BTB) or if the vtable itself
has been evicted from the data cache. More critically, indirect function
calls through a vtable cannot be easily inlined by the
compiler. Inlining eliminates call overhead and exposes further
optimizations—such as constant folding, loop unrolling, and dead code
elimination—which are all lost when virtual functions are
introduced.
Loss of Standard-Layout and Trivial Guarantees
In C++, an object with a vptr is no longer a
standard-layout type (std::is_standard_layout_v<T>
evaluates to false). This shift imposes several
performance-critical limitations:
- Trivial Copies and Moves: Standard-layout structs
with trivial types can be bulk-copied using optimized CPU memory
instructions (such as
std::memcpyor vectorized SIMD instructions). Once avptris introduced, operations inside standard containers likestd::vectorduring reallocations become non-trivial, requiring member-by-member operations or explicit move constructors. - Layout Unpredictability: In standard-layout types,
the offset of the first data member is guaranteed to be zero. With a
vptr, the implementation chooses where the pointer resides (usually at the very beginning of the object layout). This alters member offsets, complicates inter-language interoperability (e.g., C APIs), and prevents pointer casting optimizations. - Serialization and I/O: Standard-layout structs can
be directly read from or written to network sockets and disks as flat
binary blobs. Structs containing a
vptrcannot be serialized raw, as thevptrholds a process-specific memory address. Overwriting an object'svptrvia raw memory operations causes undefined behavior and segmentation faults upon the next virtual call.