Skip to main content

C++ Virtual Tables & Inheritance

Summary
C++ virtual tables (vtables) explained. Learn virtual dispatch, single/multiple inheritance, RTTI, and object memory layout visually.

Interactive Learning Mode

C++ Virtual Tables - Interactive Learning

Master vtables through guided tutorials, quizzes, and interactive visualizations

vtable
vptr
dispatch
inheritance
override

Welcome to C++ Virtual Tables

Step 1 of 8

Let's understand how C++ implements polymorphism through virtual tables!

What does the "virtual" keyword tell the compiler?

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

code
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

code
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

code
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

code
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

code
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

code
const std::type_info& info = typeid(*ptr); std::cout << info.name(); // Type name std::cout << info.hash_code(); // Type hash

dynamic_cast

code
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

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

2. Override Keyword

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

3. Final Classes/Methods

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

4. Pure Virtual Functions

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

Common Pitfalls

1. Object Slicing

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

2. Calling Virtual Functions in Constructor

code
class Base { Base() { virtualFunc(); // Calls Base::virtualFunc, not Derived! } virtual void virtualFunc(); };

3. Missing Virtual Destructor

code
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:

code
Derived d; d.virtualFunc(); // May be devirtualized

2. Final Optimization

code
class Derived final : public Base { void func() final; // Compiler can optimize };

3. CRTP (Curiously Recurring Template Pattern)

code
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)

code
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)

code
class Modern { virtual ~Modern() = default; Modern(const Modern&) = delete; // Non-copyable };

3. Virtual Function Templates (Not Allowed)

code
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