1struct Animal {2 int legs = 4;3 void kind() { puts("animal"); }4 virtual void speak() { puts("..."); }5 virtual ~Animal() { puts("~Animal"); }6};7struct Dog : Animal {8 int tricks = 2;9 void kind() { puts("dog"); }10 void speak() override { puts("Woof"); } void Speak() override { puts("Woof"); }11 ~Dog() override { puts("~Dog"); }12};$ ./zooanimalWoof$ g++ -std=c++20 zoo.cpperror: 'void Dog::Speak()' marked 'override', but does not override~Dog~AnimalDog's vtableslot: speakDog::speakslot: destroyDog::~Dog1int main() {2 std::unique_ptr<Animal> p =3 std::make_unique<Dog>();4 p->kind();5 p->speak();6}pthe Dog objectvptrlegstricks42→animalWoof~Dog~Animal!
Violet cards: the code (Animal, Dog and main)Slate boxes: the Dog object and the pointer p, faint before they are made and after they are destroyed; khaki: Dog's vtableTokens: { } a call finding the function that runs, khaki values moving, a red ! for a compile errorLeft: the class card, struct Animal and struct Dog : Animal. In front: the main() card. Right: the Dog object in memory as three boxes (the hidden vptr, the Animal part, the Dog part), the pointer p in front of it, Dog's vtable behind it and the terminal. p->kind() is not virtual, so its call goes straight to Animal's kind and prints animal. p->speak() is virtual, so its call goes through the object (its vptr and the vtable) to Dog's speak and prints Woof. At main's closing brace the Dog is deleted through p: ~Dog runs and the Dog part fades, then ~Animal and the Animal part. Precisely: a Dog's base class Animal is initialized first, then Dog's own members (tricks), then the body of Dog's constructor. Destruction runs the other way: ~Dog's body, then Dog's members, then the Animal part.

Inheritance

  1. Animal is an ordinary class: one data member, legs, and member functions. speak is marked virtual: a class built on Animal may replace it with its own version. kind is a normal, non-virtual function.

Virtual functions

Lifetime

What happens

;; step 1/10 · base
zoo.cpp   (it starts with #include <cstdio>  #include <memory>  using std::puts;)
   1  struct Animal {
   2    int legs = 4;
   3    void kind() { puts("animal"); }
   4    virtual void speak() { puts("..."); }
   5    virtual ~Animal() { puts("~Animal"); }
   6  };
virtual: a derived class may override (replace) it
puts prints its text and a new line

The two cards are one file, zoo.cpp, which starts with #include <cstdio>, #include <memory>, using std::puts; (the last line lets the code write puts instead of std::puts). Build it with g++ -std=c++20 zoo.cpp -o zoo and run ./zoo: it prints animal, Woof, ~Dog and ~Animal on four lines. The C++ standard says which function a virtual call runs, not how. The vptr and vtable shown here are how GCC, Clang and MSVC do it. With GCC and Clang on 64-bit Linux or macOS a Dog takes 16 bytes: an 8-byte vptr, then legs, then tricks (MSVC uses 24, because it does not reuse the padding at the end of Animal's part). Real tables hold more than the two slots drawn.