Inheritance and virtual functions

C++ · Practical · 15 min

Runtime polymorphism through virtual functions is how C++ code handles plugin-style designs, GUI widgets and many frameworks.

class Circle : public Shape makes Circle a Shape: it inherits Shape's members, and a Circle can be used wherever a Shape& or Shape* is expected.

A member function declared virtual in the base is dispatched at run time on the object's real type. Calling s->area() through a Shape* that points to a Circle runs Circle::area. Mark each overriding function override: the compiler then errors if the signature doesn't actually match a base virtual, which catches typos and mismatched const. A function declared = 0 is pure virtual; a class with one is abstract and can't be instantiated, which makes it an interface. final stops further overriding or derivation.

Three rules that matter in practice:

  • Give a polymorphic base class a virtual destructor (virtual ~Shape() = default;). Otherwise deleting a derived object through a base pointer, which is exactly what std::unique_ptr<Shape> does, is undefined behaviour.
  • Polymorphism only works through references and pointers. Copying a Circle into a Shape variable slices it: only the base part is copied and the virtual calls go to Shape. Hold polymorphic objects as std::unique_ptr<Base> in containers.
  • Don't call virtual functions from constructors or destructors expecting derived behaviour; during construction the object is still only a base.

Each polymorphic object carries a hidden pointer to its class's vtable, and a virtual call is an indirect call that's hard to inline. That cost is small but real in hot loops.

Use inheritance for "is-a" relationships with polymorphic behaviour. To reuse code, prefer composition (a member) over inheritance.

Example

#include <iostream>
#include <memory>
#include <vector>

class Shape {
public:
    virtual ~Shape() = default;                  // needed to delete through Shape*
    virtual double area() const = 0;             // pure virtual: Shape is abstract
    virtual const char* name() const { return "shape"; }
};

class Circle : public Shape {
    double r_;
public:
    explicit Circle(double r) : r_{r} {}
    double area() const override { return 3.14159 * r_ * r_; }
    const char* name() const override { return "circle"; }
};

class Square final : public Shape {
    double side_;
public:
    explicit Square(double s) : side_{s} {}
    double area() const override { return side_ * side_; }   // name() inherited
};

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::cout << s->name() << ' ' << s->area() << '\n';   // circle 3.14159, shape 4
}

Remember

  • virtual + override gives runtime dispatch; always write override
  • Polymorphic bases need a virtual destructor
  • Use polymorphic objects through references or pointers; copying slices them

Watch out for

  • Storing derived objects in a std::vector<Base> — they are sliced to Base. Use std::vector<std::unique_ptr<Base>>.

Check yourself

  1. `Shape s = Circle{2.0}; s.name();` — Shape::name is virtual and Circle overrides it. Which runs? (Assume Shape is not abstract.)
  2. Why does the base class need `virtual ~Shape()` when shapes are held as `std::unique_ptr<Shape>`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: en.cppreference.com/w/cpp/language/virtual