C++ inheritance and virtual functions

Call the right method through a base-class pointer, and see what slicing does.

C++
#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Animal {
 public:
  explicit Animal(std::string name) : name_(std::move(name)) {}
  virtual ~Animal() = default;
  virtual std::string sound() const { return "..."; }
  std::string speak() const { return name_ + " says " + sound(); }

 private:
  std::string name_;
};

class Dog : public Animal {
 public:
  using Animal::Animal;
  std::string sound() const override { return "Woof"; }
};

class Cat : public Animal {
 public:
  using Animal::Animal;
  std::string sound() const override { return "Meow"; }
};

int main() {
  std::vector<std::unique_ptr<Animal>> animals;
  animals.push_back(std::make_unique<Dog>("Rex"));
  animals.push_back(std::make_unique<Cat>("Tom"));
  animals.push_back(std::make_unique<Animal>("Generic"));

  for (const auto& animal : animals) {
    std::cout << animal->speak() << "\n";
  }

  Animal sliced = *animals[0];
  std::cout << "sliced copy: " << sliced.speak() << "\n";
}
Output
Rex says Woof
Tom says Meow
Generic says ...
sliced copy: Rex says ...

Explanation

A derived class inherits the members of its base class. Without virtual, a call through a base reference or pointer uses the base class's version. Marking the base function virtual makes the call dispatch at run time to the version of the actual object. override on the derived function asks the compiler to check that it really overrides a virtual function.

A class meant to be used through base pointers needs a virtual destructor, otherwise deleting a derived object through a base pointer is undefined behaviour. Copying a derived object into a base object slices it: the derived part is lost, and the copy behaves as a plain base. Hold polymorphic objects by pointer or reference, for example in std::vector<std::unique_ptr<Animal>>.

Explore the dry run
Guided walkthroughReady
1#include <iostream>
2#include <memory>
3#include <string>
4#include <vector>
5
6class Animal {
7 public:
8 explicit Animal(std::string name) : name_(std::move(name)) {}
9 virtual ~Animal() = default;
10 virtual std::string sound() const { return "..."; }
11 std::string speak() const { return name_ + " says " + sound(); }
12
13 private:
14 std::string name_;
15};
16
17class Dog : public Animal {
18 public:
19 using Animal::Animal;
20 std::string sound() const override { return "Woof"; }
21};
22
23class Cat : public Animal {
24 public:
25 using Animal::Animal;
26 std::string sound() const override { return "Meow"; }
27};
28
29int main() {
30 std::vector<std::unique_ptr<Animal>> animals;
31 animals.push_back(std::make_unique<Dog>("Rex"));
32 animals.push_back(std::make_unique<Cat>("Tom"));
33 animals.push_back(std::make_unique<Animal>("Generic"));
34
35 for (const auto& animal : animals) {
36 std::cout << animal->speak() << "\n";
37 }
38
39 Animal sliced = *animals[0];
40 std::cout << "sliced copy: " << sliced.speak() << "\n";
41}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
virtual lets derived classes replace this function and be called through a base pointer.—
—
override checks that this really overrides a base virtual function.—
—
A Dog is stored through a pointer to Animal.—
—
speak is written once in Animal. Each call runs the sound of the actual object.—
Rex says Woof
Tom says Meow
Generic says ...
Copying the Dog into a plain Animal keeps only the Animal part. The Dog behaviour is lost.—
Rex says Woof
Tom says Meow
Generic says ...
The copy is an Animal, so it says the base version.—
Rex says Woof
Tom says Meow
Generic says ...
sliced copy: Rex says ...

Cost and common mistakes

A virtual call adds a small constant cost, one indirection through the vtable.

  • Without a virtual destructor, deleting a Dog through an Animal pointer does not run Dog's destructor. unique_ptr<Animal> would then leak or misbehave.
  • Taking a polymorphic parameter by value, as in void f(Animal a), slices the argument. Take it by reference or pointer.
  • Do not call virtual functions from constructors or destructors. At that point the object is not yet, or no longer, the derived type.

Try it yourself

What does the vector print if sound is not declared virtual?

Reveal answer and reasoning

Rex says ..., Tom says ..., and Generic says ..., because a call through the base class would always use Animal::sound.

Further reading