C++ inheritance and virtual functions
Call the right method through a base-class pointer, and see what slicing does.
#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";
}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
#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";}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
| Step / line | What happens | State after step | Output 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.