Lesson 12 / 25
Inheritance, Virtual Functions and Polymorphism
Use virtual functions, override, abstract classes and virtual destructors correctly.
Run-time polymorphism in C++
A class can inherit from a base class with class Circle : public Shape. For run-time polymorphism, base-class member functions must be declared virtual; derived classes override them, and calls through a base reference or pointer dispatch to the derived version, usually via a virtual table. Mark overrides with override so the compiler checks the signature, and use final to forbid further overriding or inheritance. A pure virtual function (virtual double area() const = 0;) makes the class abstract: it cannot be instantiated, only derived from. Any base class meant to be deleted through a base pointer needs a virtual destructor; otherwise deleting a derived object through a base pointer is undefined behaviour and typically leaks the derived part. Beware object slicing: copying a derived object into a base value (Shape s = circle;) slices off the derived part, so polymorphism works only through references and pointers. C++ also supports multiple inheritance; use it mainly for interface-like abstract classes.
Abstract base class with virtual functions
Polymorphism through references and smart pointers.
#include <memory>
#include <numbers>
#include <print>
#include <vector>
class Shape {
public:
virtual ~Shape() = default; // virtual destructor
virtual double area() const = 0; // pure virtual: Shape is abstract
virtual const char* name() const { return "shape"; }
};
class Circle final : public Shape {
public:
explicit Circle(double r) : r_(r) {}
double area() const override { return std::numbers::pi * r_ * r_; }
const char* name() const override { return "circle"; }
private:
double r_;
};
class Square final : public Shape {
public:
explicit Square(double s) : s_(s) {}
double area() const override { return s_ * s_; }
const char* name() const override { return "square"; }
private:
double s_;
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(1.0));
shapes.push_back(std::make_unique<Square>(2.0));
for (const auto& s : shapes) {
std::print("{} has area {:.2f}\n", s->name(), s->area()); // dynamic dispatch
}
}Forgetting virtual on the destructor
std::unique_ptr<Shape> p = std::make_unique<Circle>(1.0); destroys the Circle through a Shape pointer. Without a virtual destructor in Shape, only Shape's destructor runs, which is undefined behaviour. Base classes with virtual functions should almost always have a virtual destructor.
Quick check: What is object slicing?
- Copying a derived object into a base-class value, losing the derived part
- Splitting a class into two files
- Calling a virtual function
- Deleting an object twice
Answer
Copying a derived object into a base-class value, losing the derived part — Base-class values hold only the base part; use references or pointers for polymorphism.