Skip to content

Polymorphism Cheatsheet

One interface, several behaviours — decided at run time from the object's real type. The rules below are short; the mistakes they prevent are the most expensive in the course.

Pairs with Module 7 — Polymorphism


The three annotations

class Shape
{
public:
    virtual ~Shape() = default;                  // polymorphic base: virtual destructor
    virtual double area() const = 0;             // pure virtual: derived classes must provide it
    virtual void describe(std::ostream& out) const;
};

class Circle : public Shape
{
public:
    double area() const override { return pi_ * r_ * r_; }   // override: compiler checks the match
};
Annotation Where Meaning
virtual Base declaration only Call is resolved from the object's real type
override Derived declaration Verify that this really overrides something
final Either No further overriding
= 0 Base declaration Pure virtual — the class is abstract, and derived classes must implement it

Always write override. Without it, a signature that differs by const silently declares a new function instead of overriding, and your program calls the base version forever.

class Derived : public Base
{
public:
    int value() override { return 2; }   // base declares: virtual int value() const
};
error: 'int Derived::value()' marked 'override', but does not override
error: 'virtual int Base::value() const' was hidden [-Werror=overloaded-virtual=]
note:   by 'int Derived::value()'

The missing const is the whole bug, and override is what turned it into a compiler error instead of a wrong answer.


Static versus dynamic binding

Binding Chosen Requires
Static (default) At compile time, from the expression's declared type —
Dynamic At run time, from the object's real type virtual, and a pointer or reference
class Base { public: void speak() const { std::cout << "base speaks\n"; } };      // not virtual
class Derived : public Base { public: void speak() const { std::cout << "derived speaks\n"; } };

int main()
{
    Derived d;
    Base& ref = d;
    ref.speak();      // static binding: prints "base speaks"
    d.speak();        // direct call: prints "derived speaks"
}
base speaks
derived speaks

Dispatch needs a pointer or a reference. Calling through a value uses the object's static type — that is exactly what slicing is.


Object slicing

Putting a derived object into a base-class value copies only the base part. The derived data and behaviour are gone, and nothing warns you.

std::vector<Animal> by_value;
by_value.push_back(Dog());
by_value[0].speak();        // prints the base behaviour
...
woof

The first line is the sliced Animal; the second is the same Dog reached through a pointer. Hold polymorphic objects by pointer, never by value:

std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(2.0));
for (const auto& s : shapes) { std::cout << s->area() << '\n'; }   // dispatches

The virtual destructor rule

Any class with a virtual function needs a virtual destructor. Without it, delete through a base pointer runs only the base destructor — the derived members are never released, and there is no warning and no error.

class Base
{
public:
    Base()  { ++alive; }
    ~Base() { --alive; }          // NOT virtual
};

class Derived : public Base
{
public:
    Derived()  { ++derived_alive; }
    ~Derived() { --derived_alive; }
};
Base* p = new Derived();
delete p;
base alive: 0
derived alive: 1

The derived object is still "alive" after being deleted: its destructor never ran. The fix is one word — virtual ~Base(); — and the reason is that delete on a base pointer cannot know the real type unless the destructor is virtual.


Abstract classes and interfaces

Concept Rule
Abstract class Has at least one pure virtual function; cannot be instantiated
Interface An abstract class with no data members and only pure virtual functions, plus a virtual destructor
Concrete class Overrides every inherited pure virtual function, and therefore can be created
Destructor of an interface Must be virtual (and can be = default)
error: cannot declare variable 's' to be of abstract type 'Shape'
note:   because the following virtual functions are pure within 'Shape':
note:     'virtual double Shape::area() const'

The second note names exactly which function is still unimplemented — read it, it is the fix.


Virtual calls during construction and destruction

A virtual call in a constructor or destructor is resolved to the class being constructed or destroyed, not the real type. During Base's constructor, the object is a Base; the derived part does not exist yet.

class Base { public: Base() { speak(); } virtual void speak() const { std::cout << "base\n"; } };
class Derived : public Base { public: void speak() const override { std::cout << "derived\n"; } };

Constructing a Derived prints base. If you need derived behaviour at construction, pass the data in and do the work in a separate init()-style call from the caller — not from the constructor.


Checklist

  • [ ] Does every polymorphic base have a virtual ~?
  • [ ] Does every overriding function say override?
  • [ ] Are polymorphic objects held by pointer or reference — never by value?
  • [ ] Is anything deleted through a base pointer that lacks a virtual destructor?
  • [ ] Are the base's functions meaningful for every derived type (Liskov)?
  • [ ] Is there any virtual call from a constructor or destructor?
  • [ ] Is the interface free of data members?

Related: Inheritance · Common OOP Bugs · SOLID · Module 7 — Polymorphism