Dynamic Dispatch vs Static Binding in C++

Dynamic dispatch and static binding are the two fundamental mechanisms C++ uses to resolve function calls. This article explains how both approaches work under the hood, examines the role of virtual tables in runtime method resolution, and compares their performance trade-offs, flexibility, and practical use cases to help you choose the right approach for your software architecture.

What Is Static Binding?

Static binding, also known as early binding, occurs when the compiler resolves a function call at compile time. In C++, static binding is the default behavior for standard non-virtual member functions, free functions, overloaded functions, and template instantiations.

When code calls a statically bound function, the compiler replaces the call directly with the target function's memory address or inlines the function body entirely. Because the target is fixed before the program runs, there is zero runtime overhead.

class Printer {
public:
    void print() { // Statically bound
        // Implementation
    }
};

Printer p;
p.print(); // Resolved at compile time

What Is Dynamic Dispatch?

Dynamic dispatch, or late binding, defers the resolution of a function call until runtime. This mechanism enables runtime polymorphism, allowing a program to invoke derived-class overrides through a base-class pointer or reference.

In C++, dynamic dispatch is triggered by declaring a member function with the virtual keyword.

class Shape {
public:
    virtual void draw() const = 0; // Dynamically dispatched
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void draw() const override {
        // Draw circle
    }
};

void render(const Shape& s) {
    s.draw(); // Resolved at runtime based on the actual object type
}

The Mechanism: Vtables and Vptrs

C++ implementations typically achieve dynamic dispatch using a Virtual Method Table (vtable) and a virtual pointer (vptr):

  1. The Vtable: The compiler generates a static table of function pointers for each class containing virtual functions.
  2. The Vptr: Each instance of that class contains a hidden pointer (vptr) pointing to the class's vtable.
  3. Execution: When a virtual function is called through a base reference or pointer, the program dereferences the object's vptr, looks up the appropriate function pointer in the vtable by index, and executes the function.

Key Differences

Feature Static Binding Dynamic Dispatch
Resolution Time Compile time Runtime
Enabling Keywords Default (also templates, overloading) virtual, override
Performance Maximum speed; enables compiler inlining Indirection overhead; usually inhibits inlining
Memory Overhead None Extra pointer (vptr) per object; vtable per class
Flexibility Lower; types must be fixed at compile time High; allows runtime extensibility and polymorphism

Performance and Design Considerations

Static binding provides optimal execution speed. Because the compiler knows the exact function being invoked, it can perform optimizations such as function inlining, dead code elimination, and loop vectorization. It is ideal for high-performance loops, utility routines, and compile-time generic programming via templates (often called static polymorphism).

Dynamic dispatch introduces a minor cost: a pointer dereference to read the vtable, an offset lookup, and an indirect jump. While this overhead is negligible in most business logic, it can impact cache locality and prevent inlining in tight, performance-critical loops. Dynamic dispatch is best suited for heterogeneous data structures, plugin architectures, and scenarios where an application's behavior must change based on runtime input.