An Animal* can point to a Dog, yet ptr->speak() still calls Dog::speak(). The pointer's static type determines which operations are available; the object's dynamic type determines the virtual implementation.
The diagrams follow that call through a typical vptr and vtable implementation. C++ specifies virtual dispatch behavior, while the compiler's application binary interface (ABI) determines the representation. The Itanium C++ ABI describes the table groups used by many GCC and Clang targets; other ABIs can differ.
From object to function call
Inspect how an Animal pointer reaches a Dog implementation. Switch topics to compare object layout, shared tables, slicing and call cost; use the dispatch and slicing controls to follow each transition.
Follow a virtual call
Trace Animal and Dog through object creation, dispatch and slicing. Choose a topic to inspect its diagram and source.
Virtual dispatch in action
Follow the pointer, object, vptr and function entry. Each playback step exposes one part of the lookup.
Read the Animal pointer
The dynamic type is Dog, so the final overrider is Dog::speak(). The diagram shows an indirect call; an optimizer may devirtualize this particular source.
Dog myDog;Animal* ptr = &myDog;ptr->speak();Complete runnable example
#include <iostream>
struct Animal {
virtual ~Animal() = default;
virtual void speak() { std::cout << "Some sound\n"; }
};
struct Dog : Animal {
void speak() override { std::cout << "Woof!\n"; }
};
struct Cat : Animal {
int age = 5;
float weight = 4.0f;
void speak() override { std::cout << "Meow!\n"; }
};
struct Flying { virtual void fly() {} };
struct Swimming { virtual void swim() {} };
struct Duck : Flying, Swimming {
void fly() override {}
void swim() override {}
};
int main() {
Dog myDog;
Animal* ptr = &myDog;
ptr->speak();
Animal myAnimal = myDog;
myAnimal.speak();
Animal& ref = myDog;
ref.speak();
std::cout << "Cat: " << sizeof(Cat) << " bytes, alignment "
<< alignof(Cat) << '\n';
std::cout << "Duck: " << sizeof(Duck) << " bytes\n";
}
Compile with a C++17 compiler. The program prints the dispatch results and your compiler's Cat and Duck sizes.
From one base to several
Compare single, multiple and diamond inheritance. Change the selected class to inspect inherited function targets, then switch to Memory Layout or RTTI to connect the object structure to pointer adjustment.
Inspect tables and inheritance
Compare single, multiple and virtual inheritance. Follow the selected class into its object layout and virtual function targets.
Selected target: B::~B(). Complete object size: 16 bytes in this model.
A, B and C share a primary base layout. B overrides Bar; C overrides Foo. Inherited functions retain their original target.
Read B layout and function targets as text
- Offset 0: vptr
- 8 bytes
- Offset 8: A::a
- 4 bytes
- Offset 12: B::b
- 4 bytes
Virtual targets: B::~B(), B::Bar(), A::Foo().
Complete runnable single inheritance example
#include <iostream>
struct A {
virtual ~A() = default;
virtual void Bar() {}
virtual void Foo() {}
int a = 0;
};
struct B : A {
void Bar() override {}
int b = 0;
};
struct C : B {
void Foo() override {}
int c = 0;
};
int main() {
std::cout << sizeof(A) << ' ' << sizeof(B) << ' ' << sizeof(C) << '\n';
}
What are Virtual Tables?
Virtual tables (vtables) are a common implementation of runtime polymorphism. A virtual table contains function pointers and ABI metadata. A class may need a primary table and secondary tables for its base subobjects; complete objects of the same most-derived type share that table data.
Key Components
1. Virtual Table Pointer (vptr)
- In this ABI model, polymorphic objects contain one or more hidden vptrs
- Each points to an address point within the appropriate virtual table
- A primary vptr is commonly at offset zero; secondary base subobjects can have their own
- The examples assume 8-byte pointers; this is a target assumption, not a C++ requirement
2. Virtual Table (vtable)
- Compiler-emitted data containing function pointers and ABI metadata
- Primary and secondary tables can form a virtual table group
- Inherited entries can point to base implementations; overrides select derived implementations
- Derived tables are generated with the appropriate targets; an override does not mutate the base class table
3. Virtual Dispatch Process
Base* ptr = new Derived();
ptr->virtualFunc(); // Virtual dispatch
// Behind the scenes:
// 1. Access object's vptr
// 2. Follow vptr to vtable
// 3. Index into vtable for function
// 4. Call function through pointer
Inheritance Types
Single Inheritance
class A {
public:
virtual ~A() {}
virtual void foo() {}
int a;
};
class B : public A {
public:
virtual void foo() override {}
int b;
};
// Memory layout:
// B object: [vptr][a][b]
// One vtable pointer
Multiple Inheritance
class Base1 {
public:
virtual ~Base1() {}
virtual void func1() {}
};
class Base2 {
public:
virtual ~Base2() {}
virtual void func2() {}
};
class Derived : public Base1, public Base2 {
public:
virtual void func1() override {}
virtual void func2() override {}
};
// Memory layout:
// Derived: [vptr1][Base1 data][vptr2][Base2 data][Derived data]
// Multiple vtable pointers!
Diamond Problem & Virtual Inheritance
class Animal {
public:
virtual ~Animal() {}
int age;
};
class Mammal : virtual public Animal {
public:
virtual void walk() {}
};
class Bird : virtual public Animal {
public:
virtual void fly() {}
};
class Bat : public Mammal, public Bird {
public:
// Single instance of Animal
};
Memory Layout Details
Alignment Rules
- Class Alignment: Must satisfy the ABI requirements of its bases and members, including hidden implementation data.
- Padding: Keeps subobjects aligned and rounds the complete size to its alignment. Some base tail padding can be reused.
- vtable Pointer: A primary vptr commonly occupies offset zero; multiple inheritance can introduce additional vptrs.
- Inheritance Order: Base subobjects, shared virtual bases and derived members follow ABI layout rules. Their layout is not a portable concatenation of class sizes.
Size Calculation
class Example {
public:
virtual ~Example() {} // +8 bytes (vptr)
int x; // +4 bytes
char c; // +1 byte
// +3 bytes padding
double d; // +8 bytes
}; // Typical model: 24 bytes, alignment 8; verify with sizeof/alignof
RTTI (Run-Time Type Information)
type_info Class
const std::type_info& info = typeid(*ptr);
std::cout << info.name(); // Type name
std::cout << info.hash_code(); // Type hash
dynamic_cast
Base* base = new Derived();
// Safe downcasting
Derived* derived = dynamic_cast<Derived*>(base);
if (derived) {
// Cast successful
}
// Cross-casting (multiple inheritance)
Base2* base2 = dynamic_cast<Base2*>(base1);
Performance Considerations
- dynamic_cast: Checked downcasts and cross-casts may perform runtime type lookup and pointer adjustment. Failed pointer casts return
nullptr; failed reference casts throwstd::bad_cast. - static_cast: Does not perform the runtime downcast check. It may still adjust a pointer, and an invalid downcast is unsafe.
- Virtual dispatch: An indirect call can load the vptr and a function pointer. Optimization can devirtualize or inline a known target.
- Memory overhead: Depends on vptr count, base subobjects, alignment and shared table data. One 8-byte vptr is the simple example, not a universal per-object cost.
Best Practices
1. Virtual Destructor
class Base {
public:
virtual ~Base() = default; // Required for ordinary deletion through Base*
};
2. Override Keyword
class Derived : public Base {
public:
void func() override; // Explicit override
};
3. Final Classes/Methods
class Derived final : public Base { // Cannot be inherited
public:
void func() final; // Cannot be overridden
};
4. Pure Virtual Functions
class Interface {
public:
virtual void func() = 0; // Pure virtual
};
Common Pitfalls
1. Object Slicing
Copying into a base object slices the derived part
In the example below, Base b = d loses the derived part of the object, while Base& ref = d keeps a reference to the original object.
Derived d;
Base b = d; // Slicing! Loses derived part
Base& ref = d; // OK - no slicing
2. Calling Virtual Functions in Constructor
class Base {
public:
Base() {
virtualFunc(); // Calls Base::virtualFunc, not Derived!
}
virtual void virtualFunc();
};
3. Missing Virtual Destructor
Base* ptr = new Derived();
delete ptr; // Undefined behavior if ~Base() not virtual
Performance Optimization
1. Devirtualization
Compiler optimizations can eliminate virtual calls when type is known:
Derived d;
d.virtualFunc(); // May be devirtualized
2. Final Optimization
class Derived final : public Base {
public:
void func() final; // Compiler can optimize
};
3. CRTP (Curiously Recurring Template Pattern)
template<typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
class Derived : public Base<Derived> {
public:
void implementation() { /* ... */ }
};
Modern C++ Features
1. override and final (C++11)
struct Base {
virtual void foo();
virtual void bar() final;
};
struct Derived : Base {
void foo() override;
// void bar() override; // Error: final
};
2. = default and = delete (C++11)
class Modern {
public:
virtual ~Modern() = default;
Modern(const Modern&) = delete; // Non-copyable
};
3. Virtual Function Templates (Not Allowed)
class Invalid {
public:
template<typename T>
virtual void func(); // Error! Cannot be virtual
};
Conclusion
Understanding vtables and inheritance is crucial for:
- Writing efficient polymorphic code
- Debugging memory layouts
- Optimizing performance
- Avoiding common pitfalls
Use the object model to reason about dispatch and ownership, and measure the actual workload before changing a design for performance. Neither the illustrated lookup steps nor the object sizes establish a universal latency cost.
For the language rules, see virtual functions in the C++ working draft. For implementation details, see Itanium object layout and runtime type information.
Related concepts
Follow a C++ function from tokens to a simplified AST, then inspect exact source ranges, declaration references and types in a keyboard-accessible explorer.
Understand the complete C++ compilation pipeline from source code to object files. Learn preprocessing, parsing, code generation, and optimization stages.
Master C++ OOP concepts including inheritance, polymorphism, virtual functions, and modern object-oriented design principles with interactive examples.
How C++ object files are linked into executables. Learn symbol resolution, static vs dynamic linking, and linker optimization.
C++ compiler optimization lab notebook — compare optimization levels, inspect compiler rewrites, diagnose auto-vectorization, and build production verification commands.
Understand C++ pointers and references with step-by-step memory diagrams: addressing, dereferencing, aliasing, array arithmetic, and object lifetime.
