SeniorCodeOccasionalNot answered yet
Sketch the vtable layout and vptr setup for a hierarchy
Given the polymorphic hierarchy below, sketch the conceptual vtable for each class (which slots, which function each slot resolves to) and explain how the hidden vptr is set during construction.
Address explicitly:
- one static vtable per polymorphic class, one
vptrper instance (not per-class storage) - each constructor stage resets
vptrto its own class's vtable — so a virtual call inside a constructor does not reach a derived override - multiple inheritance: how many
vptrs a derived object carries
struct Animal {
virtual void speak() { std::puts("..."); }
virtual void move() { std::puts("move"); }
virtual ~Animal() = default;
};
struct Dog : Animal {
void speak() override { std::puts("woof"); }
// move() not overridden
};
Sketch the layout and explain the vptr behaviour.
Each polymorphic class has a static vtable; each instance carries a hidden vptr. Each constructor stage resets the vptr to its class's vtable — so virtual calls in constructors don't reach derived overrides.
- ✗Thinking the vtable is per-instance — it is per-class (static data)
- ✗Assuming virtual inheritance does not introduce additional vptr complexity
- ✗Using reinterpret_cast to read the vptr directly — non-portable ABI hack
- →What is a pure virtual function and how does it appear in the vtable?
- →How does multiple inheritance affect vtable layout?
Contents
Hierarchy
struct Animal {
virtual void speak() { std::puts("..."); }
virtual void move() { std::puts("move"); }
virtual ~Animal() = default;
};
struct Dog : Animal {
void speak() override { std::puts("woof"); }
// move() not overridden
};
Conceptual vtable layout
Animal vtable:
[0] Animal::~Animal (destructor thunk)
[1] Animal::speak
[2] Animal::move
Dog vtable:
[0] Dog::~Dog (destructor thunk)
[1] Dog::speak ← overrides Animal::speak
[2] Animal::move ← inherited, not overridden
Each Animal or Dog instance starts with a hidden vptr:
Dog object in memory:
+0 vptr → &Dog_vtable
+8 (Dog data members, if any)
Construction sequence
Dog d;
// Step 1: Animal constructor runs — vptr set to &Animal_vtable
// Animal::Animal() body executes
// Step 2: Dog constructor runs — vptr updated to &Dog_vtable
// Dog::Dog() body executes
// Result: d.vptr == &Dog_vtable
This is why Animal::Animal() calling speak() dispatches to Animal::speak — at that point vptr still points to Animal_vtable.
Checking at runtime (non-portable, for illustration)
// On most ABI-conformant implementations:
void** vptr = *reinterpret_cast<void***>(&d);
// vptr[1] is the slot for speak()
// Never do this in production code.
Multiple inheritance and vtable
struct A { virtual void f(); };
struct B { virtual void g(); };
struct C : A, B {};
C instances contain two vptrs (one per base), and the layout looks like:
C object:
+0 vptr_A → C's A-subobject vtable (with C::f)
+8 (A data)
+N vptr_B → C's B-subobject vtable (with C::g)
+N+8 (B data)Contents