Inside the Vtable: How C++ Achieves Polymorphism
Ever wondered what happens behind the scenes when you call a virtual function in C++? I mean, how does the compiler know which function to…
Inside the Vtable: How C++ Achieves Polymorphism
Ever wondered what happens behind the scenes when you call a virtual function in C++? I mean, how does the compiler know which function to call at runtime?
Well, the answer lies in something called vtable (Virtual Table). But wait, before we dive into vtables, let me ask you something- have you ever thought why virtual functions are “costlier” than regular functions? Or why people say “there’s a hidden cost to polymorphism”?
Hmm… If you’re curious about these questions, then you’re in the right place! Today we’re gonna explore the mysterious world of vtables and see how C++ actually achieves runtime polymorphism.
First Things First: What Problem Are We Solving?
Let me show you a simple code to understand what we’re dealing with:
class Animal {
public:
void speak() { cout << "Some sound" << endl; }
};
class Dog : public Animal {
public:
void speak() { cout << "Woof!" << endl; }
};
int main() {
Animal* ptr = new Dog();
ptr->speak(); // Output: Some sound
return 0;
}
Oops! Even though we’re pointing to a Dog object, we get “Some sound” instead of “Woof!”. This is because without virtual, the compiler uses static binding- it decides at compile time to call Animal's speak() based on the pointer type.
Now let’s make it virtual:
class Animal {
public:
virtual void speak() { cout << "Some sound" << endl; }
};
class Dog : public Animal {
public:
void speak() { cout << "Woof!" << endl; }
};
int main() {
Animal* ptr = new Dog();
ptr->speak(); // Output: Woof!
return 0;
}
Yesss! Now we get “Woof!” as expected. But HOWWW...? This is where vtables come into play!
So, What Exactly is a Vtable?
A vtable (or Virtual Function Table) is basically a lookup table of function pointers. Think of it as a menu card in a restaurant- you look at the menu (vtable) to see what dishes (functions) are available, and then you order (call) the one you want.
Here’s what happens internally:
- Every class that has at least one virtual function gets its own vtable
- This vtable is created at compile time (yes, compile time!)
- Each object of that class contains a hidden pointer called vptr (virtual pointer) that points to its class’s vtable
Umm… sounds theoretical? Let me draw a simple diagram to make it clearer:

fig-1: Virtual Pointer to VTable Mapping
The Hidden Cost: Your Object Just Got Bigger!
Here’s something interesting- when you add virtual functions to a class, your objects become larger! Let me prove it:
class WithoutVirtual {
int data;
public:
void func() { }
};
class WithVirtual {
int data;
public:
virtual void func() { }
};
int main() {
cout << "Size without virtual: " << sizeof(WithoutVirtual) << endl;
cout << "Size with virtual: " << sizeof(WithVirtual) << endl;
return 0;
}
Output (on a 64-bit system):
Size without virtual: 4
Size with virtual: 16
Whoa! The object with virtual function is 16 bytes instead of 4. That extra space? It’s the vptr (8 bytes on 64-bit systems) plus padding for alignment.
How Does the Compiler Build These Vtables?
The compiler is pretty smart about this. Here’s what it does:
- During Compilation: It scans each class and creates a vtable for classes with virtual functions
- Vtable Contents: For each virtual function, it adds an entry with the most derived version’s address
- Object Construction: When you create an object, the constructor secretly sets up the vptr to point to the correct vtable
Let’s see this in action with a bit more complex example:
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
void func3() { cout << "Base::func3" << endl; } // Not virtual
};
class Derived : public Base {
public:
void func1() override { cout << "Derived::func1" << endl; }
// func2 not overridden
void func3() { cout << "Derived::func3" << endl; }
};
The vtables would look like:

fig-2: Base-Derived VTable Structure Comparison
The Performance Trade-offs
Now you may think- “Is this vtable thing slow?”
Well, there’s definitely some overhead:
- Memory Cost: Each object needs extra space for vptr (usually 8 bytes on 64-bit systems)
- Indirection Cost: Calling a virtual function involves:
- Load the vptr from the object
- Load the function address from vtable
- Call the function
This is basically two extra memory accesses compared to a regular function call!
3. Cache Misses: The vtable might not be in CPU cache, causing additional delays
4. Inlining Prevention: Virtual functions generally can’t be inlined (compiler doesn’t know which function will be called)
But hold on! Before you go removing all virtual functions from your code, remember- this overhead is usually negligible compared to the benefits of polymorphism. Unless you’re writing a game engine or high-frequency trading system, you probably won’t notice the difference.
Can We See the Vtable in Action?
If you’re really curious (and a bit adventurous), you can actually peek at the vtable using some pointer tricks:
class Test {
public:
virtual void func1() { cout << "func1" << endl; }
virtual void func2() { cout << "func2" << endl; }
};
int main() {
Test t;
// Warning: This is implementation-specific and not portable!
void** vtable = *(void***)&t;
cout << "Vtable address: " << vtable << endl;
cout << "func1 address: " << vtable[0] << endl;
cout << "func2 address: " << vtable[1] << endl;
return 0;
}
The Bottom Line
So now you know the secret behind C++’s runtime polymorphism! Vtables are basically the compiler’s clever way of implementing dynamic dispatch. Yes, there’s a cost- a bit of extra memory and indirection, but in return, we get the power of polymorphism.
Think about it- without vtables, we’d have to write tons of if-else statements or switch cases to achieve the same behavior. Isn’t a small performance cost worth the elegant code we can write with virtual functions?
Remember: “premature optimization is the root of all evil”. Use virtual functions when they make your design cleaner and more maintainable. Only optimize when you’ve measured and found an actual performance problem!
Reference — ISO C++ Standard and Others
Hey, did you like the explanation?
*If yes then don’t forget to follow **exploreIT **as I regularly come up with interesting topics related to programming. And of course, *if you have any difficulty understanding the concepts or find anything wrong in the article please feel free to comment below!
Feel free to connect — https://www.linkedin.com/in/salman-shaikh-82989b1b9/
See you in the next article, Bye Bye!
메타데이터
- post_id
- 698be2229eec
- slug
- inside-the-vtable-how-c-achieves-polymorphism-698be2229eec
- url
- https://medium.com/@exploreit-askdoubt/inside-the-vtable-how-c-achieves-polymorphism-698be2229eec
- canonical_url
- https://medium.com/@exploreit-askdoubt/inside-the-vtable-how-c-achieves-polymorphism-698be2229eec
- author_url
- https://medium.com/@exploreit-askdoubt
- status
- ok
- fetched_at
- 2026-07-17 23:41:04