Why do we need a virtual table?
I was looking for some information about virtual tables, but I can't find anything that is easy to understand.
Can somebody give me good examples with explanations?
Without virtual tables you wouldn't be able to make runtime polymorphism work since all references to functions would be bound at compile time. A simple example
struct Base {
virtual void f() { }
};
struct Derived : public Base {
virtual void f() { }
};
void callF( Base *o ) {
o->f();
}
int main() {
Derived d;
callF( &d );
}
Inside the function callF
, you only know that o
points to a Base
object. However, at runtime, the code should call Derived::f
(since Base::f
is virtual). At compile time, the compiler can't know which code is going to be executed by the o->f()
call since it doesn't know what o
points to.
Hence, you need something called a "virtual table" which is basically a table of function pointers. Each object that has virtual functions has a "v-table pointer" that points to the virtual table for objects of its type.
The code in the callF
function above then only needs to look up the entry for Base::f
in the virtual table (which it finds based on the v-table pointer in the object), and then it calls the function that table entry points to. That might be Base::f
but it is also possible that it points to something else - Derived::f
, for instance.
This means that due to the virtual table, you're able to have polymorphism at runtime because the actual function being called is determined at runtime by looking up a function pointer in the virtual table and then calling the function via that pointer - instead of calling the function directly (as is the case for non-virtual functions).
The virtual function table is an implementation detail - it's how the compiler implements polymorphic methods in classes.
Consider
class Animal
{
virtual void talk()=0;
}
class Dog : Animal
{
virtual void talk() {
cout << "Woof!";
}
}
class Cat : Animal
{
virtual void talk() {
cout << "Meow!";
}
}
And now we have
A* animal = loadFromFile("somefile.txt"); // from somewhere
animal->talk();
How do we know which version of talk()
is called? The animal object has a table that points to the virtual functions that are used with that animal. For example, talk
may be at the 3rd offset, if there are two other virtual methods:
dog
[function ptr for some method 1]
[function ptr for some method 2]
[function ptr for talk -> Dog::Talk]
cat
[function ptr for some method 1]
[function ptr for some method 2]
[function ptr for talk -> Cat::Talk]
When we have an instance of Animnal
, we don't know which talk()
method to call. We find it by looking in the virtual table and fetching the third entry, since the compiler knows that corresponds to the talk
pointer (the compiler knows the virtual methods on Animal, and so knows the order of pointers in the vtable.)
Given an Animal, to call the right talk() method, the compiler adds code to fetch the 3rd function pointer and use that. This then directs to the appropriate implementation.
With non-virtual methods, this is not necessary since the actual function being called can be determined at compile time - there is only one possible function that can be called for a non-virtual call.
To answer your headline question - you don't, and the C++ Standard does not specify that you must be provided with one. What you do want is to be able to say:
struct A {
virtual ~A() {}
virtual void f() {}
};
struct B : public A {
void f() {}
};
A * p = new B;
p->f();
and have B::f called and not A::f. A virtual function table is one way of implementing this, but is frankly not of interest to the average C++ programmer - I only ever think about it when answering questions like this.
Short answer: virtual function call, basePointer->f(), means different things depending on the history of basePointer. If it points to something that Really is a derived class, a different function will be called.
For this, compiler does a simple game of function pointers. Addresses of functions to be called for different types are stored in virtual table.
Virtual table is not used only for function pointers. The RTTI machinery uses it for run-time type information (obtaining actual types of an object referenced by an address of one of the base types).
Some new/delete implementations would store the object size in the virtual table.
Windows COM programming uses virtual table to crack into it and push it as an interface.