Virtual Table (vtable), Virtual Pointer (vptr), and Object Slicing in C++
C++ is a language that supports both compile-time (static) and runtime (dynamic) polymorphism. Understanding how virtual functions work under the hood is crucial for interviews. This article covers everything about vtable, vptr, and object slicing, including all subtle cases, examples, and analogies.
1️⃣ Virtual Functions
A virtual function is a member function declared with the virtual keyword in a base class and meant to be overridden in derived classes.
Example:
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() {
cout << "Base class show function" << endl;
}
};
class Derived : public Base {
public:
void show() override {
cout << "Derived class show function" << endl;
}
};
int main() {
Base* b;
Derived d;
b = &d;
b->show(); // Calls Derived::show because of virtual function
}
Output:
Derived class show function
Key point:
Without
virtual,b->show()would call Base::show → compile-time resolution.Virtual functions enable dynamic (runtime) polymorphism.
Analogy:
- Think of a virtual function like a restaurant menu item that can be cooked differently in different branches (derived classes). Which branch cooks it is decided when the order is placed (runtime).
2️⃣ vtable and vptr
2.1 What is vtable?
vtable (virtual table) is a compiler-generated table of function pointers for all virtual functions in a class.
It contains pointers to the most derived version of each virtual function.
Example:
class Base {
public:
virtual void f1() { cout << "Base f1\n"; }
virtual void f2() { cout << "Base f2\n"; }
};
class Derived : public Base {
public:
void f1() override { cout << "Derived f1\n"; }
};
- Base vtable:
| Function | Address |
| f1 | Base::f1 |
| f2 | Base::f2 |
- Derived vtable:
| Function | Address |
| f1 | Derived::f1 |
| f2 | Base::f2 |
2.2 What is vptr?
vptr (virtual pointer) is a hidden pointer in each object pointing to the vtable of its class.
Each object of a class with virtual functions has its own vptr, even though all objects of the class share the same vtable.
Analogy:
Object = Customer
vptr = pointer in the customer’s hand to the restaurant menu (vtable)
Calling a virtual function = customer asking the chef to cook a dish → menu tells which chef (function) to call.
2.3 How vtable and vptr work together
Object contains vptr pointing to its class vtable.
Calling a virtual function → compiler uses vptr to look up the function address in vtable → calls correct function.
Diagram Example:
Base* b = new Derived();
b (object)
+-------------------+
| vptr -> vtable |
+-------------------+
vtable (Derived)
+-------------------+
| f1 -> Derived::f1 |
| f2 -> Base::f2 |
+-------------------+
b->f1() calls Derived::f1
b->f2() calls Base::f2
3️⃣ Base class without virtual, Derived class with virtual
class Base {
public:
void normalFunc() { cout << "Base normal function\n"; }
};
class Derived : public Base {
public:
virtual void virtualFunc() { cout << "Derived virtual function\n"; }
};
Base* b;
Derived d;
b = &d;
// b->virtualFunc(); // ❌ Error: Base has no virtualFunc
Base: no virtual functions → no vtable, no vptr.
Derived: has virtual functions → vtable and vptr created.
Base pointer cannot access Derived’s virtual functions → slicing or error.
Memory layout for Derived object:
+----------------+
| vptr -> vtable |
+----------------+
| Base members |
+----------------+
4️⃣ Inspecting vptr and vtable
Derived d;
void** vptr = *(void***)&d; // Get vptr
typedef void(*FunPtr)();
FunPtr f = (FunPtr)vptr[0]; // Get first function
f(); // Calls Derived::func
vptr can be accessed like any hidden pointer (compiler-dependent).
vtable is per class, vptr per object.
Step by step explanation
Derived d;Creates a Derived object in memory.
Since Derived has virtual functions, the compiler adds a hidden pointer (
vptr) insided.vptrpoints to Derived’s vtable, which contains addresses of all virtual functions.
void** vptr = *(void***)&d;
(void***)&d→ Treats the address of the object as a pointer to pointer (vptr is at the beginning of object memory).*→ Dereferences it to get the actual vptr stored in the object.
Now vptr points to the vtable array.
Typedef:
typedef void(*FunPtr)();
What it means
typedef→ we are creating a new name for a type.void(*FunPtr)();→ this is the type of a pointer to a function that:takes no parameters
()returns void
void
So after this line:
typedef void(*FunPtr)();
FunPtrbecomes a shortcut name for "pointer to a function that takes no arguments and returns void".Example:
void myFunction() { cout << "Hello\n"; }
FunPtr f = &myFunction; // f now points to myFunction
f(); // Calls myFunction
✅ Key idea: FunPtr is just a function pointer type.
Accessing vtable entry
FunPtr f = (FunPtr)vptr[0];
Step by step
vptr→ points to the vtable array of the object.vptr[0]→ first entry in the vtable → the address of the first virtual function.(FunPtr)vptr[0]→ cast the address to a function pointer type so you can call it like a normal function.FunPtr f = ...→ nowfholds a pointer to that virtual function.
Calling the function
f(); // Calls the function via vtable
This executes the first virtual function of the object, using the vtable pointer.
If the object is
Derived, it calls Derived’s version, not Base’s.
Analogy
vtable = menu board (list of dishes / functions)
vptr = pointer in the customer’s hand pointing to menu
vptr[0]→ first dish on menu(FunPtr)vptr[0]→ tell your hand “treat this menu item as callable function”f();→ order the dish → the right chef (function) cooks it
Example without virtual for clarity
#include <iostream>
using namespace std;
void foo() { cout << "Foo called\n"; }
typedef void(*FunPtr)(); // FunPtr = pointer to function taking void returning void
int main() {
FunPtr f = &foo; // f points to foo
f(); // Calls foo
}
Output:
Foo called
✅ Works exactly like what we did with vtable, just the function pointer comes from vtable instead of directly from a function.
5️⃣ Can vptr be static?
No, vptr is per object → dynamic pointer to vtable.
Static means class-level → shared by all objects.
Analogy: Static = billboard shared by all, vptr = personal menu card for each customer.
6️⃣ Static vs Dynamic Dispatch
Static (Compile-Time)
Function is non-virtual → resolved at compile-time.
Example:
class Base {
public:
void show() { cout << "Base show\n"; }
};
class Derived : public Base {
public:
void show() { cout << "Derived show\n"; }
};
Base* b;
Derived d;
b = &d;
b->show(); // Calls Base::show
- The pointer type (
Base*) decides the function, not the actual object.
Analogy: Fixed menu → same chef always cooks.
Dynamic (Runtime)
Function is virtual → resolved at runtime using vptr and vtable.
Example:
Base* b = &d;
b->show(); // Calls Derived::show
Analogy: Menu adapts to actual restaurant (object) → chef depends on object.
7️⃣ Object Slicing
Definition:
- When a derived object is assigned to a base object by value, the derived-specific members and vptr are lost.
Example
class Base {
public:
int a = 1;
virtual void show() { cout << "Base show\n"; }
};
class Derived : public Base {
public:
int b = 2;
void show() override { cout << "Derived show\n"; }
};
Derived d;
Base b = d; // Object slicing
b.show(); // Calls Base::show
bonly has Base part,Derivedmembers lost,vptrpoints to Base vtable.
Analogy: Ice cream cone: Base = vanilla scoop, Derived = chocolate scoop. Pouring into Base-sized cup → chocolate scoop (derived) is lost.
Memory layout comparison
Derived object (before slicing):
+----------------+
| vptr -> Derived vtable |
+----------------+
| Base member a=1 |
+----------------+
| Derived member b=2 |
+----------------+
Base object (after slicing):
+----------------+
| vptr -> Base vtable |
+----------------+
| Base member a=1 |
+----------------+
8️⃣ Avoiding Slicing
- Use pointers or references:
Base* bptr = &d;
bptr->show(); // Calls Derived::show
Base& bref = d;
bref.show(); // Calls Derived::show
- Passing objects by value causes slicing.
9️⃣ Slicing with Multiple Objects
| Assignment | Slicing? |
| Derived1 → Derived1 | ❌ No |
| Derived1 → Base (Base object) | ✅ Yes |
| Derived1 → Derived2 (different branches) | ❌ Error (direct) |
| Derived1 → Derived2 via Base* | ✅ Partial (Base part copied) |
Analogy:
Tree branches: Base = trunk, Derived1 = left branch, Derived2 = right branch.
Assigning along same branch → full copy
Assigning to trunk → extra parts lost
Assigning across branches → compiler error unless via trunk pointer
10️⃣ vptr in Object Assignment
Derived1 d1, d2;
d2 = d1; // Assignment
Both objects have vptr pointing to Derived1 vtable.
Assignment copies vptr value → still points to the same vtable.
Each object has its own vptr slot, stored separately in memory.
Analogy:
- vtable = blueprint of house (shared), vptr = address sign outside each house (per object). Copying house copies the address sign value, but each house has its own sign post.
11️⃣ Memory and vtable Recap
| Concept | Description |
| vtable | Table of virtual function addresses, per class |
| vptr | Hidden pointer in object to vtable, per object |
| Virtual functions | Resolved dynamically using vptr + vtable |
| Non-virtual functions | Resolved statically at compile time |
| Object slicing | Copies only base part, derived members + vptr lost |
| Avoid slicing | Use pointers or references |
12️⃣ Interview Tips
Always mention memory layout when discussing vtable/vptr.
Explain dynamic vs static dispatch using simple analogy.
Object slicing → explain via derived to base assignment + pointers/references solution.
Multiple inheritance → mention multiple vptrs.
Pure virtual functions still occupy vtable entries.
13️⃣ Analogies Summary
vtable: Restaurant menu with function pointers
vptr: Pointer in customer’s hand to menu
Dynamic dispatch: Menu decides which chef cooks (runtime)
Static dispatch: Fixed menu, chef determined at compile-time
Object slicing: Ice cream cone → chocolate scoop (derived) lost when pouring into base cup
Derived assignment (same type): Full copy → no slicing
Derived assignment via base: Only Base part copied → slicing
✅ Conclusion
This article has covered every aspect of virtual functions, vtable, vptr, object slicing, and related subtle points:
Virtual function mechanism
vtable and vptr structure
Static vs dynamic dispatch
Base without virtual / derived with virtual
Object slicing: causes, memory layout, how to avoid
Assignment nuances: same branch vs different branch
vptr behavior in object assignment
Analogies for better understanding
With this knowledge, you can answer any interview question related to virtual tables, virtual pointers, and object slicing in C++ confidently
Tricky C++ Virtual Function & Object Slicing Interview Questions
1️⃣ Question: Base without virtual, Derived with virtual
class Base { int a; };
class Derived : public Base { virtual void f() {} };
Base* b;
Derived d;
b = &d;
b->f(); // What happens?
Answer:
❌ Compilation error: Base has no member
f().vtable exists only for Derived, Base object/pointer doesn’t know about it.
Key insight: Virtual functions in derived don’t “create” virtual functions in the base automatically.
Explanation/Analogy:
Base = generic menu
Derived = special menu item
Pointer to Base cannot see the special menu item → compiler error.
2️⃣ Question: Object slicing
class Base { virtual void show() { cout << "Base\n"; } };
class Derived : public Base { void show() override { cout << "Derived\n"; } };
Derived d;
Base b = d;
b.show();
Answer:
Base
Object slicing occurred →
bis only Base part ofd.vptrof Derived object lost → Base vtable used.Key takeaway: Assigning by value to base object slices off derived members and vptr.
Tricky insight: Even though show() is virtual, Base object doesn’t know about Derived → Base function called.
3️⃣ Question: Multiple inheritance and vptr
class A { virtual void f() { cout << "A\n"; } };
class B { virtual void g() { cout << "B\n"; } };
class C : public A, public B { void f() override { cout << "C::A\n"; } };
int main() {
C c;
A* pa = &c;
B* pb = &c;
pa->f();
pb->g();
}
Answer:
C::A
B
Chas two vptrs, one for each base subobject (AandB).papoints to A subobject → callsC::f()via A’s vtable.pbpoints to B subobject → calls B’s function via B’s vtable.
Tricky insight: Multiple inheritance → multiple vptrs. Each vptr handles its base part independently.
4️⃣ Question: Virtual destructor importance
class Base { public: ~Base() { cout << "Base\n"; } };
class Derived : public Base { public: ~Derived() { cout << "Derived\n"; } };
Base* b = new Derived();
delete b;
Answer:
Base
❌ Derived destructor not called because Base destructor is non-virtual.
Memory leak or undefined behavior may occur if Derived has dynamic memory.
Correct way:
class Base { public: virtual ~Base() { cout << "Base\n"; } };
- Now,
delete b→ calls Derived::~Derived() first, then Base::~Base().
Tricky insight: Always declare virtual destructor in base if deleting via base pointer.
5️⃣ Question: Pure virtual function in base class
class Base { virtual void f() = 0; };
class Derived : public Base { void f() override { cout << "Derived\n"; } };
Derived d;
d.f();
Answer:
Derived
Pure virtual function still has vtable entry.
Derived overrides it → vtable entry updated.
Tricky insight: You cannot instantiate Base, but Derived has complete vtable → virtual calls work.
6️⃣ Question: Static vs dynamic dispatch
class Base { public: void f() { cout << "Base\n"; } };
class Derived : public Base { public: void f() { cout << "Derived\n"; } };
Base* b;
Derived d;
b = &d;
b->f();
Answer:
Base
f()is non-virtual → static dispatchCompiler looks at pointer type (
Base*) → calls Base::f()Tricky insight: Virtual keyword is crucial for runtime polymorphism.
7️⃣ Question: Assigning one derived object to another derived object
Derived1 d1;
Derived2 d2;
d2 = d1; // Will this compile?
Answer:
❌ Compilation error: Derived1 and Derived2 are unrelated types.
Partial slicing only happens if assigned via Base pointer/reference:
Base* b = &d2;
*b = d1; // Only Base part copied → slicing
Tricky insight: Object slicing occurs when target is smaller, assignment across unrelated derived classes directly → error.
8️⃣ Question: vptr after assignment between same type objects
Derived1 d1, d2;
d2 = d1;
Both objects have vptr pointing to Derived1’s vtable.
Assignment copies vptr value, but each object has its own vptr slot in memory.
Tricky insight: vptr value may be same → points to same vtable, but stored separately per object.
9️⃣ Question: Virtual function in derived, base has no virtual
class Base { };
class Derived : public Base { virtual void f() { cout << "Derived\n"; } };
Base* b = new Derived();
b->f();
❌ Compilation error → Base doesn’t have member
f().vtable exists only for Derived, base pointer cannot see it.
Tricky insight: Virtual in derived does not create virtual in base automatically.
10️⃣ Question: Passing derived to base by value
void func(Base b) { b.show(); }
Derived d;
func(d);
Object slicing happens → only Base part copied.
Virtual function called in Base object → Base version executed.
Tricky insight: Always pass by pointer or reference to preserve polymorphism.
Summary of Tricky Points
Base without virtual → Derived virtual functions cannot be called via base pointer.
Object slicing → derived members and vptr lost when copying to base object.
Multiple inheritance → multiple vptrs, one per base subobject.
Virtual destructors → must declare in base to ensure correct deletion via base pointer.
Pure virtual functions → occupy vtable entry.
Static vs dynamic dispatch → pointer type vs actual object type.
Assignment between derived objects → slicing only occurs if LHS is “smaller” type (Base).
vptr per object → copied during assignment but stored separately.
Virtual function in derived alone → cannot be accessed through base pointer.
Always use pointers or references to avoid slicing.
Other tricky concepts and doubts
1️⃣ Base pointer, virtual function in Base, new virtual in Derived
Scenario 1: Base has virtual fn(), Derived has virtual gn()
#include <iostream>
using namespace std;
class Base {
public:
virtual void fn() { cout << "Base fn\n"; }
};
class Derived : public Base {
public:
virtual void gn() { cout << "Derived gn\n"; }
};
int main() {
Derived d;
Base* b = &d;
b->fn(); // ✅ Allowed
// b->gn(); // ❌ Error: Base has no gn()
}
Explanation
b->fn()→ calls Derived’s overridden fn() if it exists.- Virtual function → resolved dynamically using
vptrofd.
- Virtual function → resolved dynamically using
b->gn()→ not allowed, Base doesn’t havegn()→ compiler error.vtable only contains virtual functions of the pointer’s class (Base) and any overridden ones.
Derived-only functions are not visible via Base pointer.
Memory layout:
Derived object d:
+-----------------+
| vptr -> Derived vtable |
+-----------------+
| Base members |
+-----------------+
Derived vtable contains entries for:
fn()(overridden),gn()(new).Base pointer sees only Base vtable entries → can only call
fn().
Analogy:
Base pointer = generic remote, can press only Base buttons.
Derived remote = extra buttons (gn), inaccessible via Base pointer.
2️⃣ Base pointer, simple (non-virtual) functions
class Base {
public:
void fn() { cout << "Base fn\n"; }
};
class Derived : public Base {
public:
void gn() { cout << "Derived gn\n"; }
};
int main() {
Derived d;
Base* b = &d;
b->fn(); // Calls Base::fn()
// b->gn(); // Error
}
Explanation
Non-virtual → static dispatch, function call resolved at compile time.
b->fn()always calls Base::fn(), ignoring Derived object.Base pointer cannot see Derived-only functions (
gn).
Analogy: Fixed menu → chef always follows pointer type, not object.
3️⃣ Object slicing and vptr loss
Scenario
class Base {
public:
virtual void fn() { cout << "Base fn\n"; }
};
class Derived : public Base {
public:
virtual void gn() { cout << "Derived gn\n"; }
};
int main() {
Derived d;
Base b = d; // Object slicing
b.fn(); // Calls Base::fn()
// b.gn(); // Error
}
Explanation
Base b = d;→ Base part of Derived copiedDerived members (
gnfunction entry in vtable) lost.vptr of
d(pointing to Derived vtable) not copied properly →buses Base vtable.
b.fn()→ Base vtable → Base::fn() calledb.gn()→ compiler error, gn does not exist in Base
Memory:
d (Derived)
+-------------------+
| vptr -> Derived vtable |
+-------------------+
| Base members |
+-------------------+
| Derived members |
+-------------------+
b (Base after slicing)
+-------------------+
| vptr -> Base vtable |
+-------------------+
| Base members |
+-------------------+
Takeaway: vptr of RHS lost → Base object calls Base functions only, cannot dispatch to Derived.
What “vptr lost” really means
vptr inside the Base object
bnow points to Base vtableAny Derived-specific virtual functions in
d(likegn()) are not copied intob.This is what we mean by RHS vptr lost in object slicing.
✅ Key: The Base object does not know about Derived’s virtual functions.
5️⃣ Multiple derived classes, Base pointer/reference
class Base { public: virtual void fn() { cout << "Base fn\n"; } };
class Derived1 : public Base { public: void fn() override { cout << "Derived1 fn\n"; } };
class Derived2 : public Base { public: void fn() override { cout << "Derived2 fn\n"; } };
int main() {
Base* b1 = new Derived1();
Base* b2 = new Derived2();
b1->fn(); // Derived1 fn
b2->fn(); // Derived2 fn
}
Explanation
Each Derived object has its own vptr pointing to its respective vtable.
Base pointers call
fn()dynamically → resolved via object’s vptr → correct derived function called.
Memory layout:
b1 -> Derived1 object
vptr -> Derived1 vtable
Base members
b2 -> Derived2 object
vptr -> Derived2 vtable
Base members
- Each vptr points to its own class vtable, so virtual dispatch works independently for multiple derived objects.
