Skip to content

Module 7 — Summary

The essentials

  • The static type of the expression decides the call unless the function is virtual and the call is made through a reference or pointer. A call on a named object is always statically bound — even when the function is virtual — because the compiler knows the exact type.
  • virtual is opt-in, per function, and it decides the behaviour of every loop written later. The choice is made once, in the base class, and it is invisible at the call site: 1200000 where 780000 was expected, with no diagnostic anywhere.
  • Any base class that may be destroyed through a base pointer needs a virtual destructor, written as virtual ~Base() = default;. Without one, only the base destructor runs and the standard makes it undefined behaviour — so the symptom is not a message you can learn to recognise, which is why the rule is preventive.
  • A copy constructs an object of the static type. By-value parameters, containers of objects, and Base b = derived; all slice. Polymorphic parameters and containers take handles: const Base&, Base*, std::unique_ptr<Base>.
  • A virtual call during construction or destruction resolves to the class being constructed or destroyed, not the most-derived type. Compliance is silent; if the function is pure virtual, the call is undefined behaviour.
  • override makes the compiler check your claim; final refuses a claim someone else might make. Both are free, and both convert silent mistakes into build failures. An override replaces the base implementation — to keep the base's work, call it by qualification.
  • Polymorphism costs one hidden pointer per object and one indirection per call. The program measures both: sizeof(Shape) = 8, sizeof(Circle) = 16, against a tagged struct's 24 bytes. The benefit is not speed — it is that adding a kind stops being an edit in four places that must agree.
  • A switch on a type tag and a hierarchy are two ways of writing the same decision. The refactor between them changes nothing about the output and everything about the cost of the next change. -Wswitch protects the switches that name the enum; it does not protect the design.

The one-sentence version

Declare virtual where a subclass may legitimately change the answer, always give a polymorphic base a virtual destructor, pass polymorphic objects only by reference or pointer, and never call a virtual function from a constructor or destructor.

Terms you must now be able to define

Polymorphism · virtual function · override · override and final · static binding · dynamic binding · dispatch · vtable / vptr (as a typical implementation) · virtual destructor · object slicing · polymorphic value semantics · pure virtual function (preview) · abstract class (preview) · dispatch during construction and destruction

See the glossary. Three matter most for what follows: pure virtual function and abstract class are Module 8's entire subject, and ownership of polymorphic objects is what Modules 10 and 11 exist to make safe.

The habit to keep

Run the declared-type test on every call site you find surprising. Write one line: static type of the expression, virtual yes/no, handle form (reference, pointer, or object). Two minutes, and it resolves the vast majority of dispatch bugs — while the third of the three conditions explains why a call in a constructor cannot dispatch and a call on a named object does not need to.

Second habit, once per hierarchy: grep for the type name with a space or a < after it. void f(Person, std::vector<Person>, Person person = — every hit is a slice, and the compiler will not tell you about any of them.

Before you move on

  • [ ] Warm-up and core exercises attempted, with results checked
  • [ ] Lab 7 builds with no warnings, keeps the recorded failure, and its leak counter returns to zero
  • [ ] You can write the declared-type test for a call site in under a minute
  • [ ] You have reproduced a slice on purpose and fixed it with handles
  • [ ] You can explain the four-answer trace without looking at the program
  • [ ] You can name three places virtual would be the wrong choice, with a reason each
  • [ ] Self-assessment ≥ 80%, with question 10's four defects all found
  • [ ] At least one challenge problem attempted, with the rubric applied
  • [ ] The skills checklist on the module page ticked with evidence

Further reading

  • Effective C++ (3rd ed.), Items 34–36 and 40 — the interface/implementation split, virtual functions and the alternatives to them, never redefining an inherited non-virtual function, and why multiple inheritance should be interface-only. Item 34's "alternative to virtual functions" list is the honest counterweight to this module's enthusiasm.
  • *Effective Modern C++, Items 17 and 21 — virtual destructors, and the std::unique_ptr that makes polymorphic ownership routine. Read 21 with Module 11.
  • The C++ Programming Language (4th ed.), the chapter on class hierarchies — the language's own account of abstract classes, dispatch and the areas where virtual functions cost something.
  • The C++ Standard, [class.virtual] and [expr.call] — dispatching, overriding and the rule about calling virtual functions during construction and destruction, in the normative wording. Nine paragraphs, and the source of every claim above.
  • Design Patterns (GoF), the introduction's section on dynamic binding, and the Strategy and Template Method patterns — the shapes that virtual functions make possible and Module 15 names.
  • C++ Core Guidelines — C.35 (a base class destructor should be either public and virtual or protected and non-virtual), C.67/C.146 (don't slice; store and pass polymorphic objects by pointer or reference), and C.128 (virtual, override and final — use exactly one of them per declaration).

See Further Reading for editions and why these are the right ones.


Next: Module 8 — Abstract Classes. Module 8 takes the hierarchy you can now dispatch and turns the base into a contract: a class that cannot be instantiated, whose pure virtual functions every concrete type must implement. It is also where the running system finally loses the design smell this module kept pointing at — a base field that means nothing for some of its derived classes.