Vtables: Know by Many Different Names
It's worth a few brain cells to remember that a vtable is known by many different names: virtual
function table, virtual method table, and even as a dispatch table. When interviewing,
it's always a good idea to be familiar with the terminology.
Vtables: Used Behind the Scenes of Polymorphism
When working with virtual functions in C++, it's the vtable that's being used behind
the scenes to help
achieve polymorphism. And, although you can understand polymorphism without understanding
vtables, many interviewers like to ask this question just to see if you really know
your stuff.
Vtables contain pointers to virtual functions
Whenever a class itself contains virtual functions or overrides virtual functions
from a parent class the compiler builds a vtable for that class. This means that not all
classes have a vtable created for them by the compiler.
The vtable contains function pointers
that point to the virtual functions in that class. There can only be one vtable per class,
and all objects of the same class will share the same vtable.
Vpointers point to the vtable
Associated with every vtable is what's called a vpointer. The vpointer points
to the vtable, and is used to access the functions inside the vtable.
The vtable would be useless without a vpointer.
For any class that contains a vtable, the compiler will also add "hidden" code to the constructor of that class to initialize
the vpointers of its objects to the address of the corresponding vtable.
Because you're probably confused now, let's take a look at an example so that we
can explain vtables more clearly and in more detail.
Take a look at the code below:
class Animal // base class
{
public:
int weight;
virtual int getWeight() {};
}
// Obviously, Tiger derives from the Animal class
class Tiger: public Animal {
public:
int weight;
int height;
int getWeight() {return weight;};
int getHeight() {return height;};
int main()
{
Tiger t1;
/* below, an Animal object pointer is set to point
to an object of the derived Tiger class */
Animal *a1 = &t1;
/* below, how does this know to call the
definition of getWeight in the Tiger class,
and not the definition provided in the Animal
class */
a1 -> getWeight();
}
}
Only one vtable per class
The vtable contains function pointers
that point to the virtual functions in that class. It's important to note that there can only be one vtable per class,
and all objects of the same class will share the same vtable.
This means that in the example above, the Animal and Tiger classes
will each have their very own vtable, and any objects of the Animal or Tiger classes will use their respective class's
vtables.
Vtables are used at runtime
In the example above, because the Tiger class overrides the getWeight virtual function provided in the
Animal class, the compiler will construct a vtable for the Tiger class. This vtable will only contain a function
pointer for the getWeight function. The getHeight function will not be put
inside the vtable because it is not virtual, nor does it override a virtual function in the Animal base class.
The question that the code above raises is at runtime how does the call "a1 -> getWeight()" know to
use the version of getWeight provided in the Tiger - and notthe Animal class. The answer,
as you probably guessed, is by using vtables.
We now know that a vtable will be created for the Tiger class. And every vtable must have a vpointer that points
to the vtable (otherwise the vtable can not be referenced).
Let's call the vpointer that belongs to the Tiger class vptr1 - this is just a name we created
so we can show our point.
Take a look at the code below again.
// Obviously, Tiger derives from the Animal class
class Tiger: public Animal {
/*
...some code left out here
*/
int main()
{
Tiger t1;
/* below, an Animal object pointer is set to point
to an object of the derived Tiger class */
Animal *a1 = &t1;
/* below, how does this know to call the
definition of getWeight in the Tiger class,
and not the definition provided in the Animal
class */
a1 -> getWeight();
}
}
Because the Tiger class contains a pointer to the vtable called vptr1, the call "a1 -> getWeight()" will
actually be translated to "(*(a1 -> vptr1 -> getWeight())".
Tiger t1;
/* below, an Animal object pointer is set to point
to an object of the derived Tiger class */
Animal *a1 = &t1;
a1 -> getWeight();
/*
The call above gets translated to this,
assuming the pointer to the vtable for the
Tiger class is called vptr1
:
*(a1 -> vptr1 -> getWeight())
*/
This means that the definition of getWeight() provided in the Tiger class will be called, which
is what we want.
What if vtables were not used?
Now that we've seen vtables in action we should ask ourselves again
why C++ would use vtables? Hopefully at this point you can answer that question
yourself. But, just to review, vtables are used so that the correct
definition of the function can be called at runtime - basically to help
achieve polymorphism. In our example, we wanted the definition of the
getWeight() function provided in the Tiger class to be called. And that is exactly
what will happen with the help of vtables.
Join our mailing list to receive more questions from actual programmer interviews!
|