C++ Virtual Tables & Inheritance
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
- • 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
- • 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
- Class Alignment: Aligned to largest member
- Padding: Added to maintain alignment boundaries
- vtable Pointer: Always at offset 0 (usually)
- 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.