Skip to main content

Command Palette

Search for a command to run...

Virtual Table (vtable), Virtual Pointer (vptr), and Object Slicing in C++

Published
17 min read

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::showcompile-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:
FunctionAddress
f1Base::f1
f2Base::f2
  • Derived vtable:
FunctionAddress
f1Derived::f1
f2Base::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

  1. Object contains vptr pointing to its class vtable.

  2. 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

    1. Derived d;

      • Creates a Derived object in memory.

      • Since Derived has virtual functions, the compiler adds a hidden pointer (vptr) inside d.

      • vptr points to Derived’s vtable, which contains addresses of all virtual functions.


  1. 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.


  1. 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)();
  • FunPtr becomes 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.


  1. Accessing vtable entry

    FunPtr f = (FunPtr)vptr[0];

Step by step

  1. vptr → points to the vtable array of the object.

  2. vptr[0] → first entry in the vtable → the address of the first virtual function.

  3. (FunPtr)vptr[0] → cast the address to a function pointer type so you can call it like a normal function.

  4. FunPtr f = ... → now f holds a pointer to that virtual function.


  1. 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
  • b only has Base part, Derived members lost, vptr points 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

AssignmentSlicing?
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

ConceptDescription
vtableTable of virtual function addresses, per class
vptrHidden pointer in object to vtable, per object
Virtual functionsResolved dynamically using vptr + vtable
Non-virtual functionsResolved statically at compile time
Object slicingCopies only base part, derived members + vptr lost
Avoid slicingUse pointers or references

12️⃣ Interview Tips

  1. Always mention memory layout when discussing vtable/vptr.

  2. Explain dynamic vs static dispatch using simple analogy.

  3. Object slicing → explain via derived to base assignment + pointers/references solution.

  4. Multiple inheritance → mention multiple vptrs.

  5. 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 → b is only Base part of d.

  • vptr of 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
  • C has two vptrs, one for each base subobject (A and B).

  • pa points to A subobject → calls C::f() via A’s vtable.

  • pb points 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 dispatch

  • Compiler 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

  1. Base without virtual → Derived virtual functions cannot be called via base pointer.

  2. Object slicing → derived members and vptr lost when copying to base object.

  3. Multiple inheritance → multiple vptrs, one per base subobject.

  4. Virtual destructors → must declare in base to ensure correct deletion via base pointer.

  5. Pure virtual functions → occupy vtable entry.

  6. Static vs dynamic dispatch → pointer type vs actual object type.

  7. Assignment between derived objects → slicing only occurs if LHS is “smaller” type (Base).

  8. vptr per object → copied during assignment but stored separately.

  9. Virtual function in derived alone → cannot be accessed through base pointer.

  10. 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

  1. b->fn() → calls Derived’s overridden fn() if it exists.

    • Virtual function → resolved dynamically using vptr of d.
  2. b->gn()not allowed, Base doesn’t have gn() → 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

  1. Base b = d;Base part of Derived copied

    • Derived members (gn function entry in vtable) lost.

    • vptr of d (pointing to Derived vtable) not copied properlyb uses Base vtable.

  2. b.fn() → Base vtable → Base::fn() called

  3. b.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 b now points to Base vtable

  • Any Derived-specific virtual functions in d (like gn()) are not copied into b.

  • 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

  1. Each Derived object has its own vptr pointing to its respective vtable.

  2. 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.