Concepts / Understanding Virtual Functions in C++

Understanding Virtual Functions in C++

A caller making a virtual call needs only two facts: the location of the pointer to the vtbl, and the index of each virtual function.

  • Programming

Why a Caller Cannot Simply Look Inside

Imagine a piece of code that receives an object and needs to ask it to do something, such as draw itself or report its area. The caller would like to write one request and have the right behavior happen, whatever the object turns out to be. The difficulty is that the caller may not know how big the object is or how its data members are arranged in memory. If the caller had to know those details for every possible kind of object, the code would be fragile and tied to each concrete class.

The virtual function table, usually called the vtbl, is the mechanism that removes this need. Its job is to let the object be used correctly even when the caller has no knowledge of the object's size or internal data layout. This article follows that mechanism step by step, so you can see what the caller actually does and what the object provides.

The Two Facts Behind Every Virtual Call

Picture the moment a virtual call happens. The caller is holding an object, but it does not look inside that object to find the code it wants. Instead, it relies on two pieces of information. First, it knows where the pointer to the vtbl is located, relative to the object's container. Second, it knows the index that corresponds to the particular virtual function it wants to run. Using the pointer, the caller reaches the table. Using the index, it selects one entry in that table. The entry leads to the function that the object's class provides.

Key point

A caller making a virtual call needs only two facts: the location of the pointer to the vtbl, and the index of each virtual function.

Notice what the caller never needs. It does not need the object's data members, their order, or the total size of the object. Those details belong to the class that defines the object. The caller only needs a fixed access pattern: follow the pointer, then pick the slot by index. The two facts are kept separate because that makes the mechanism easier to reason about. The next two parts examine each one in turn.

Tracing One Virtual Call Step by Step

Now follow a single call through the mechanism. The caller holds an object and asks for a virtual function. It follows the vtbl pointer stored in that object to reach the table. It then uses the function's index to select the matching entry. The entry leads to the function the object's class supplies, and that function runs. At no point did the caller open up the object to read its data members or measure its size.

Why the Extra Step Costs Only About 25 Percent

A natural worry is that following a pointer and then choosing an index must make every virtual call much slower than an ordinary call. The reference material addresses this directly. It says the virtual call mechanism can be made almost as efficient as the normal function call mechanism, within 25 percent. In other words, the extra work of going through the table is small enough that the difference is modest in practice.

This closeness is why virtual functions are a reasonable tool to use when the design calls for them. The extra cost is the pointer step plus the index step, and both are small compared with the benefit of letting one request produce the right behavior for many kinds of object.

Counting the Memory Cost

Using the vtbl is not free in terms of memory. The source gives a precise accounting. Each object of a class with virtual functions carries one pointer, and each such class has one vtbl. The table belongs to the class and is not repeated for every object. The pointer, however, is stored in each object. This split explains why the cost scales in a useful way.

Worked example

Counting Pointers and Tables for a Small Program

A program creates 4 objects. They belong to 3 different classes, and every class has virtual functions. How many extra pointers and how many extra vtbls does the program carry?

  1. 1
    Count the pointers

    Each object carries one vtbl pointer. Four objects give 4 pointers.

  2. 2
    Count the tables

    Each class with virtual functions has one vtbl. Three classes give 3 vtbls.

  3. 3
    Notice what does not multiply

    The tables do not depend on how many objects exist. Adding more objects of these classes adds pointers but no new tables.

Result

4 extra pointers (one per object) and 3 extra vtbls (one per class).

What do you think happens?

If a program creates 10 objects that all belong to one class with virtual functions, how many vtbls exist for that class?

  • 10 vtbls, one for each object

  • 1 vtbl, shared by the class

  • 0 vtbls, because the pointers hold the functions

Reveal answer
Answer:

1 vtbl, shared by the class. The program also carries 10 pointers, one stored in each object.

The table belongs to the class and is not repeated for every object. Only the pointer is stored in each object, so the table count depends on classes, not on objects.

Mistakes When Reasoning About Virtual Calls

  • Common mistake

    Thinking the vtbl is copied into every object

    What it looks like

    Assuming that three objects of one class each carry their own full function table.

    Why it fails

    The table belongs to the class and is not repeated for every object. Only the pointer is stored in each object.

    Do this instead

    Count one pointer per object and one vtbl per class with virtual functions.

  • Common mistake

    Assuming the caller must know the object's data members

    What it looks like

    Believing that to call an area function, the caller must know where the object's width and height fields sit.

    Why it fails

    The caller needs only the location of the vtbl pointer and the index of the function. The data members belong to the class that defines the object.

    Do this instead

    Follow the pointer, then pick the slot by index.

  • Common mistake

    Assuming a virtual call is many times slower than a normal call

    What it looks like

    Expecting each virtual call to cost several times as much as a direct call, and avoiding virtual functions for that reason alone.

    Why it fails

    The 25 percent figure says the mechanism can be made almost as efficient as a normal call, within that margin.

    Do this instead

    Treat the extra cost as a small step through the table, and weigh it against the design benefit.

  • Common mistake

    Confusing the index of a function with its address

    What it looks like

    Treating the number 1 in a slot as if it were the function's memory location.

    Why it fails

    The index is a fixed slot number that selects an entry in the table. It is not the address the entry leads to.

    Do this instead

    Think of the index as a slot number, and the table entry as the thing that leads to the function.

Trace a Virtual Call Yourself

EASY

A program creates 4 objects. They belong to 3 different classes, and every class has virtual functions. Using one pointer per object and one vtbl per class, how many extra pointers and how many extra vtbls does the program carry? Then explain, in your own words, what the caller needs to know to call a virtual function on one of these objects.

Hints
  • Count the objects for the pointers, and the classes for the tables.
  • The caller needs exactly two facts: where the vtbl pointer is, and which index the function has.

Key Takeaways

  • A vtbl lets a caller use an object correctly without knowing its size or data layout.
  • A virtual call needs only two facts: the location of the vtbl pointer inside the object, and the index of the function in the table.
  • The source puts the extra cost of a virtual call within about 25 percent of a normal call.
  • Space overhead is one pointer in each object and one vtbl for each class with virtual functions, so the table does not grow with the object count.

Related concepts

More concepts starting with U