---
title: 'Virtual Functions and Vtables'
source: 'https://academia.sh/en/courses/cpp-fundamentals/virtual-functions-and-vtables'
course: 'C++ Fundamentals and Object Design'
language: en
updated: '2026-09-26T03:02:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Shows that a virtual call selects which function to run based on the object's real type"
    - 'Measures that a call through a base pointer or reference always works with the same object'
    - 'Verifies that each base pointer in an array reaches the object it itself points to, not another object'
---

# Virtual Functions and Vtables

The measurement of whether a virtual call through a base pointer or reference always works with the same object, independent of the expression's static type.

The previous lesson measured that the base subobject is a **part** of the derived
object, its address coinciding with it — but no function was `virtual`, and the
function to be called was determined at compile time by looking at the expression's
static type. This lesson adds the `virtual` keyword: now a call through a `Shape*`
or `Shape&` decides which function to run not by the pointer's static type, but by
the **real type of the object it points to**. The question has changed: it is not
the base subobject's address, but **which object** a virtual call really works with
that is measured.

## A Virtual Call Reaches the Same Object

`Rectangle` redefines `Shape`'s `introduce` function. When object `r` is accessed
both through a `Shape*` pointer and a `Shape&` reference and `introduce` is called,
does both reach `r`'s own function?

```cpp
// virtual_same_object.cpp -- virtual call reaches the same object independent of static type
#include <iostream>

class Shape {
public:
    virtual ~Shape() = default;
    virtual void introduce(const void* expected) const {
        std::cout << "introduce(Shape): running object same as expected "
                  << std::boolalpha << (static_cast<const void*>(this) == expected) << "\n";
    }
};

class Rectangle : public Shape {
public:
    void introduce(const void* expected) const override {
        std::cout << "introduce(Rectangle): running object same as expected "
                  << std::boolalpha << (static_cast<const void*>(this) == expected) << "\n";
    }
};

int main() {
    Rectangle r;
    Shape* base_ptr = &r;
    Shape& base_ref = r;

    base_ptr->introduce(&r);
    base_ref.introduce(&r);

    return 0;
}
```

```
introduce(Rectangle): running object same as expected true
introduce(Rectangle): running object same as expected true
```

**PS4.** Both calls were made through an expression whose static type is `Shape`
(`base_ptr`, `base_ref`), but both printed "introduce(Rectangle)" — the virtual
call selected `Rectangle::introduce` by looking not at the pointer's/reference's
static type, but at the real type of the object it points to. Inside the function,
comparing `this` to `r`'s address returns **true**: the call not only selects the
right function, it also runs that selected function exactly on `r` itself, not on a
copy or another object. (M08/K02 asked which type resolved the call; a similar
mechanism exists here too, but what is measured is which **object** the call works
with — and while Java has no object slicing, assigning to a base type by value in
C++ would construct a new and incomplete object; here there is only pointer/
reference access, slicing does not come into play.) The manually built method
table (a struct carrying function pointers) in M11/K03's Object-Like Design lesson
corresponds here to the compiler-generated virtual function table; the mechanism's
details were covered there and are not re-explained here. The class is **self**:
`this` always carries `r`'s address, independent of the static type of the
expression through which the call was made.

## An Array of Base Pointers: Each Call Reaches Its Own Object

When a `Shape*` array points to different derived objects, does each element reach
the object it itself points to, or is there mixing between them?

```cpp
// virtual_array.cpp -- each base pointer in the array reaches the object it itself points to
#include <iostream>

class Shape {
public:
    virtual ~Shape() = default;
    virtual bool same_as(const void* address) const {
        return static_cast<const void*>(this) == address;
    }
};

class Rectangle : public Shape {};
class Triangle : public Shape {};

int main() {
    Rectangle r;
    Triangle t;
    Shape* shapes[2] = { &r, &t };

    std::cout << std::boolalpha;
    std::cout << "shapes[0] same as its own object r "
              << shapes[0]->same_as(&r) << "\n";
    std::cout << "shapes[1] same as its own object t "
              << shapes[1]->same_as(&t) << "\n";
    std::cout << "shapes[0] same as t (cross-check) "
              << shapes[0]->same_as(&t) << "\n";

    return 0;
}
```

```
shapes[0] same as its own object r true
shapes[1] same as its own object t true
shapes[0] same as t (cross-check) false
```

**PS5.** `Rectangle` and `Triangle` do not redefine `introduce`, they inherit
`Shape::same_as` as is — but the function still binds `this` to the real object of
the element it was called through, every time it is called. `shapes[0]->same_as(&r)`
is **true**, `shapes[1]->same_as(&t)` is **true**: each element reaches its own
object. The cross-check makes this even clearer — `shapes[0]` (the element pointing
to `r`) compared against `&t` returns **false**, because the object `shapes[0]`
works with is `r`, not `t`. The class is **self**: every base pointer in the array,
even though it carries the same base type, works with no object other than the
particular object it itself points to.

## Passing to a Function by Reference Does Not Change the Object

An object passed to a function with a `const Shape&` parameter — inside the
function, is it still the same object as outside, or the function's own copy?

```cpp
// virtual_reference_parameter.cpp -- object passed by reference to a function is the same as the one outside
#include <iostream>

class Shape {
public:
    virtual ~Shape() = default;
    virtual bool same_as(const void* address) const {
        return static_cast<const void*>(this) == address;
    }
};

class Rectangle : public Shape {};

void verify(const Shape& s, const void* expected) {
    std::cout << std::boolalpha
              << "object called inside the function same as expected "
              << s.same_as(expected) << "\n";
}

int main() {
    Rectangle r;
    verify(r, &r);
    return 0;
}
```

```
object called inside the function same as expected true
```

**PS6.** In the call `verify(r, &r)`, `s` is not a copy of `r`, it is a reference
that binds to `r` itself — the `const Shape&` parameter type provides this without
constructing a copy. When `s.same_as(&r)` is called inside the function, the virtual
call again selects `Shape::same_as` (because `Rectangle` did not redefine it), and
comparing `this` to the outside `r`'s address returns **true**. Crossing the
function boundary — as long as the parameter is a reference — does not change the
object's identity: `s` inside `verify` is the same object as `r` inside `main`. The
class is **self**: as long as the function call does not construct a copy, a
virtual call always works with the real object at the point the call started.

## override and final: Still Reaching the Same Object by Real Type

`BorderedTriangle` derives from `Triangle`, marks `same_as` as both `override` and
`final`, and is itself `final`. These two keywords affect compile-time checking, not
run-time behavior — but does the virtual call that ends up running still reach the
same object?

```cpp
// virtual_override_final.cpp -- virtual call marked override and final still runs based on the object's real type
#include <iostream>

class Shape {
public:
    virtual ~Shape() = default;
    virtual bool same_as(const void* address) const {
        return static_cast<const void*>(this) == address;
    }
};

class Triangle : public Shape {
public:
    bool same_as(const void* address) const override {
        return static_cast<const void*>(this) == address;
    }
};

class BorderedTriangle final : public Triangle {
public:
    bool same_as(const void* address) const override final {
        return static_cast<const void*>(this) == address;
    }
};

int main() {
    BorderedTriangle b;
    Shape& base_ref = b;

    std::cout << std::boolalpha
              << "same_as called through the base reference same as expected "
              << base_ref.same_as(&b) << "\n";

    return 0;
}
```

```
same_as called through the base reference same as expected true
```

**PS23.** `override` tells the compiler "this function redefines a base class
function," and if the signature does not match (for example if `const` is
forgotten, or a parameter type is written wrong) it produces a compile error — this
prevents a mismatched signature from silently defining a new, separate function (and
the base function never being redefined). This protection operates at compile time;
since the sandbox only runs code that compiles successfully, how a signature
mismatch leads to an error cannot be shown by running it here, only narrated.
`final` prevents both class `BorderedTriangle` and function `same_as` from being
further derived from/redefined, at compile time — this too is something observable
not by running it, but only by a forbidden violation failing to compile. The part
that runs has not changed: `base_ref.same_as(&b)` prints **true**, because
`override`/`final` do not change run-time behavior, they only narrow which
signatures are considered valid. The class is **self**: a `final` class's object,
like any other, is exactly the object the virtual call reaches.

## Virtual Destructor: Deleting Through a Base Pointer Runs the Correct Chain

`Shape`'s destructor is `virtual`. When a `Rectangle` constructed with `new` is
`delete`d through a `Shape*`, does destruction start from `Rectangle`'s own
destructor, or jump straight to `Shape`'s?

```cpp
// virtual_destructor_delete_through_base_pointer.cpp -- virtual destructor runs the correct destruction chain when deleted through a base pointer
#include <iostream>

class Shape {
public:
    virtual ~Shape() { std::cout << "Shape destructor\n"; }
};

class Rectangle : public Shape {
public:
    ~Rectangle() override { std::cout << "Rectangle destructor\n"; }
};

int main() {
    Shape* base_ptr = new Rectangle();
    std::cout << "-- deletion begins --\n";
    delete base_ptr;
    return 0;
}
```

```
-- deletion begins --
Rectangle destructor
Shape destructor
```

**PS24.** `base_ptr`'s static type is `Shape*`, but when `delete base_ptr` is
called, **Rectangle destructor** is printed first, then **Shape destructor** —
destruction, like construction, runs from most derived to most base, and what lets
it start in the correct order is `~Shape()` being `virtual`. This is the behavior
the standard defines: when the base class's destructor is virtual, a `delete`
through a base pointer starts by calling the real (dynamic) type's destructor. The
counterpart has no defined outcome: if `~Shape()` were not `virtual`, this
`delete base_ptr`, where the static and dynamic types differ, would be **undefined
behavior** per the standard. On common compilers this usually shows up as only
`Shape`'s destructor running and the resources `Rectangle` added not being
released, but that is a compiler behavior, not a language guarantee. This branch is
not run here: since undefined behavior does not give a deterministic output, it has
no result verifiable in the sandbox, it can only be narrated. The class is **self**:
the `delete` call always runs the full destruction chain of `r` (the real object),
independent of `base_ptr`'s static type.

## A Manually Built Method Table and the Compiler's Virtual Function Table Give the Same Result

M11/K03's Object-Like Design lesson manually built a "method table" using a
`struct` that carried function pointers. Does a call through such a manually built
table and a `virtual` call through the compiler-generated virtual function table
give the same result for the same object?

```cpp
// virtual_manual_method_table.cpp -- a manually built method table gives the same result as the compiler-generated virtual function table
#include <iostream>

struct Shape {
    virtual ~Shape() = default;
    virtual int area() const { return 0; }
};

struct Square : public Shape {
    explicit Square(int side) : side_(side) {}
    int area() const override { return side_ * side_; }
    int side_;
};

struct ManualMethodTable {
    int (*area)(const void*);
};

int square_area_manual(const void* object) {
    return static_cast<const Square*>(object)->area();
}

int main() {
    Square square(5);
    Shape& virtual_access = square;

    ManualMethodTable table{ square_area_manual };

    std::cout << "compiler's virtual call: " << virtual_access.area() << "\n";
    std::cout << "manually built table call: " << table.area(&square) << "\n";
    std::cout << std::boolalpha << "both results the same "
              << (virtual_access.area() == table.area(&square)) << "\n";

    return 0;
}
```

```
compiler's virtual call: 25
manually built table call: 25
both results the same true
```

**PS25.** `virtual_access.area()` reaches `Square::area` through the
compiler-generated virtual function table; `table.area(&square)` reaches the same
function (this time directly through a function pointer) through the manually built
`ManualMethodTable`. Both print **25** and the comparison is **true**: two different
mechanisms select the same function for the same object. The difference is in how
the selection is made — in a virtual call the compiler adds a hidden virtual
function table pointer to every object, and `area()` finds the right entry through
that pointer every time it is called; in a manually built table this step is the
responsibility of the programmer here in the `ManualMethodTable table{...}` line and
matching the table to the correct object. This mechanism was covered in detail in
M11/K03; here only the fact that the two approaches arrive at the same result is
verified. The class is **self**: both calls work on `square` itself.

## The Structural Cost of Virtual Dynamic Dispatch: A Larger Object

`VirtualPoint` carries the same two `int` fields as `NonVirtualPoint`, with one
difference: it has a virtual destructor. Are these two classes, carrying the same
fields, the same size, or does carrying a virtual function have a per-object
structural cost?

```cpp
// virtual_dynamic_dispatch_cost.cpp -- a class carrying a virtual function is larger than a non-virtual class carrying the same fields
#include <iostream>

class NonVirtualPoint {
public:
    int x_ = 0;
    int y_ = 0;
};

class VirtualPoint {
public:
    virtual ~VirtualPoint() = default;
    int x_ = 0;
    int y_ = 0;
};

int main() {
    std::cout << std::boolalpha
              << "is the class carrying a virtual function larger than the one that does not "
              << (sizeof(VirtualPoint) > sizeof(NonVirtualPoint)) << "\n";
    return 0;
}
```

```
is the class carrying a virtual function larger than the one that does not true
```

**PS26.** The two classes carry exactly the same two `int` fields, but the
comparison is **true**: `VirtualPoint` is larger. The difference comes from the
hidden virtual function table pointer the compiler adds to every object as soon as
a `virtual` function is defined — this is dynamic dispatch's **per-object,
one-time** structural cost. Besides this there is also a **per-call** cost: every
virtual call performs an indirection that first finds the right entry through the
object's virtual function table pointer, instead of a direct jump. This second cost
is not measured here, because a timing measurement would depend on hardware,
compiler optimization level, and the sandbox's own load — this lesson does not
report such environment-dependent data. What is measurable and deterministic is
only the size difference. The class is **part**: the virtual function table
pointer is a part of the object, not a separate object.

## Virtual Call Inside the Constructor: Goes to the Base, Not the Derived

`Base`'s constructor calls `define()` inside its own body; `Derived` redefines this
function. When a `Derived` object is constructed, does this call inside `Base`'s
constructor reach `Derived::define`, or `Base::define`?

```cpp
// virtual_call_inside_constructor.cpp -- virtual call inside a constructor goes to the base's own function, not the derived's
#include <iostream>

class Base {
public:
    Base() {
        std::cout << "calling define() inside Base constructor\n";
        define();
    }
    virtual void define() const {
        std::cout << "Base::define ran\n";
    }
};

class Derived : public Base {
public:
    Derived() {
        std::cout << "calling define() inside Derived constructor\n";
        define();
    }
    void define() const override {
        std::cout << "Derived::define ran\n";
    }
};

int main() {
    Derived d;
    return 0;
}
```

```
calling define() inside Base constructor
Base::define ran
calling define() inside Derived constructor
Derived::define ran
```

**PS27.** `d`'s final form is a `Derived`, but while `Base`'s constructor is
running, the `define()` call inside it runs **Base::define**, not
`Derived::define`. Why: while `Base`'s constructor is running, `Derived`'s own part
has not been constructed yet — the object's effective type at that moment, from the
perspective of virtual dispatch, is still `Base`. This changes as the chain
proceeds upward: while `Derived`'s own constructor is running, the `define()` call
made inside it now runs **Derived::define**, because at that point the object's
`Derived` part has also been constructed. The class is **self**: both calls work on
`d` itself, but which function is selected depends on where the construction chain
stands at the moment the call is made.

## The Bounding Measurement

The virtual calls measured in this lesson were always made through a pointer or a
reference; assigning `Rectangle` to a `Shape` variable **by value**
(`Shape s = r;`) is outside this measurement's scope — such an assignment does not
engage the virtual call, it engages the slicing touched on at the end of the
previous lesson, and the result is a new, incomplete `Shape` object. The
measurement also covers only the simple cases where `introduce` and `same_as` do
not themselves make another virtual call inside them; the incomplete destruction
that could arise if a base destructor were not virtual was never observed in this
lesson, because both classes (`Shape` and its derivatives) carry a virtual
destructor. A base class built entirely from functions with no body at all, only
presenting an interface — abstract classes — is not this lesson's subject; the
next lesson measures that.

## Summary

- A virtual call through a `Shape*` or `Shape&` decides which function to run not
  by the expression's static type, but by the real type of the object it points to.
- Inside the selected function, `this` always carries the address of the same real
  object, independent of the base-type expression the call was made through — the
  class is **self**.
- Each element in a `Shape*` array reaches the particular object it itself points
  to; a cross-check confirms it does not match another element's object.
- An object passed to a function with a `const Shape&` parameter is the same
  object outside as it is inside the function — reference binding does not
  construct a copy.
- Assigning **by value** to the base type (slicing) is outside this lesson's
  scope; a virtual call works this way only under pointer/reference access.

## Next Step

In this lesson `Shape::introduce` and `Shape::same_as` had a body — they could be
called even though derived classes did not redefine them. The next lesson, Abstract
Classes and Interfaces, measures the case where a base class is built entirely from
functions with no body at all, declaring only a contract — there, an object cannot
be constructed directly from the base class, only from derived classes; what is
measured is again which object a virtual call works with, but this time the base
type has no instance of its own at all.
