Inheritance
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.
struct Dog : Animal makes Dog a derived class of Animal. A Dog is an Animal plus more: it inherits legs, kind and speak, adds tricks, and brings its own kind and speak. override says: this replaces a virtual function.
std::make_unique<Dog>() makes a Dog on the heap. Its Animal part is built first, so legs is 4; then its own part, so tricks is 2. One object, base part first. p now points at it.
p's type is pointer to Animal, yet it points at a Dog. That is allowed because every Dog is an Animal. Through p you may use only what Animal declares: legs, kind and speak.
Virtual functions
p->kind() runs Animal's kind and prints animal, even though the object is a Dog. kind is not virtual, so the compiler picks the version from p's type, Animal, before the program ever runs.
p->speak() prints Woof. speak is virtual, so the choice waits until the program runs and follows the object p really points at, a Dog. This is dynamic dispatch; one call, many possible behaviours is polymorphism.
How does it know? Compilers give each class with virtual functions a table of them, the vtable, and each object a hidden pointer to its class's table, the vptr. A virtual call follows the vptr, then speak's slot.
Type Speak instead of speak and, without override, Dog quietly gets a new function, and p->speak() runs Animal's version. With override the compiler refuses: Speak replaces nothing. Mark every replacement override.
Lifetime
At main's closing brace p is destroyed and deletes the Dog through its Animal pointer. ~Animal is virtual, so this call is dispatched too: ~Dog runs first, then ~Animal. Reverse order: Dog's part, then the Animal part.
Code written against an Animal pointer or reference works for every class derived from Animal, even ones written later. One rule: a base class with virtual functions needs a virtual destructor. Without it, deleting a Dog through p is undefined behaviour.
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.