C++ Virtual Tables & Inheritance

15 min

Deep dive into C++ virtual tables (vtables), virtual dispatch mechanism, inheritance types, and object memory layout

Best viewed on desktop for optimal interactive experience

Interactive Learning Mode

C++ Virtual Tables - Interactive Learning

Master vtables through guided tutorials, quizzes, and interactive visualizations

vtable
vptr
dispatch
inheritance
override
Learning Tips:
  • • Click on answer options to test your understanding
  • • Use the hint button when you're stuck
  • • Watch animations multiple times to grasp the concept
  • • Complete all sections to unlock all concept badges
  • • Review misconceptions to avoid common pitfalls

Technical Deep Dive

C++ Virtual Tables & Inheritance

Understanding vtables, virtual dispatch, and object memory layout

Key Concepts:
  • • Virtual tables enable runtime polymorphism through late binding
  • • Each polymorphic class has a vtable containing function pointers
  • • Objects contain vtable pointers (vptr) pointing to their class vtable
  • • Virtual dispatch has slight overhead (one extra pointer dereference)
  • • RTTI enables safe runtime type checking with dynamic_cast

What are Virtual Tables?

Virtual tables (vtables) are the mechanism C++ uses to implement runtime polymorphism. Each class with virtual functions has a vtable containing pointers to its virtual function implementations.

Key Components

1. Virtual Table Pointer (vptr)

  • Each object of a polymorphic class contains a hidden pointer
  • Points to the class's vtable
  • Typically the first member in object memory layout
  • Size: 8 bytes on 64-bit systems

2. Virtual Table (vtable)

  • Static array of function pointers
  • One vtable per class (shared by all instances)
  • Contains pointers to virtual function implementations
  • Updated when derived classes override virtual functions

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 { virtual ~A() {} virtual void foo() {} int a; }; class B : public A { virtual void foo() override {} int b; }; // Memory layout: // B object: [vptr][a][b] // One vtable pointer

Multiple Inheritance

class Base1 { virtual ~Base1() {} virtual void func1() {} }; class Base2 { virtual ~Base2() {} virtual void func2() {} }; class Derived : public Base1, public Base2 { 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 { virtual ~Animal() {} int age; }; class Mammal : virtual public Animal { virtual void walk() {} }; class Bird : virtual public Animal { virtual void fly() {} }; class Bat : public Mammal, public Bird { // Single instance of Animal };

Memory Layout Details

Alignment Rules

  1. Class Alignment: Aligned to largest member
  2. Padding: Added to maintain alignment boundaries
  3. vtable Pointer: Always at offset 0 (usually)
  4. Inheritance Order: Base class members before derived

Size Calculation

class Example { virtual ~Example() {} // +8 bytes (vptr) int x; // +4 bytes char c; // +1 byte // +3 bytes padding double d; // +8 bytes }; // Total: 24 bytes (aligned to 8)

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: Runtime overhead (type checking)
  • static_cast: No runtime overhead (unsafe)
  • Virtual dispatch: One extra indirection
  • Memory overhead: 8 bytes per polymorphic object

Best Practices

1. Virtual Destructor

class Base { public: virtual ~Base() = default; // Always make destructor virtual };

2. Override Keyword

class Derived : public Base { void func() override; // Explicit override };

3. Final Classes/Methods

class Derived final : public Base { // Cannot be inherited void func() final; // Cannot be overridden };

4. Pure Virtual Functions

class Interface { virtual void func() = 0; // Pure virtual };

Common Pitfalls

1. Object Slicing

Derived d; Base b = d; // Slicing! Loses derived part Base& ref = d; // OK - no slicing

2. Calling Virtual Functions in Constructor

class Base { 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 { void func() final; // Compiler can optimize };

3. CRTP (Curiously Recurring Template Pattern)

template<typename Derived> class Base { void interface() { static_cast<Derived*>(this)->implementation(); } }; class Derived : public Base<Derived> { 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 { virtual ~Modern() = default; Modern(const Modern&) = delete; // Non-copyable };

3. Virtual Function Templates (Not Allowed)

class Invalid { 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

The overhead of virtual functions is minimal for most applications, but understanding the mechanism helps make informed design decisions.

If you found this explanation helpful, consider sharing it with others.

Mastodon