Inheritance, virtual functions, and polymorphism
Model interfaces with inheritance, override virtual functions, and use virtual destructors safely.
Learning objectives
- Explain base and derived classes.
- Use virtual functions and override.
- Understand runtime polymorphism through a base reference.
- Explain why a polymorphic base class needs a virtual destructor when deleted through a base pointer.
Prerequisites: classes, constructors, access control, and references. Review Classes and objects, Constructors and destructors, and References first.
Inheritance models an is-a relationship
Inheritance lets a derived class reuse and extend a base class interface and implementation. Use it when the derived type genuinely can be treated as the base type. Prefer composition when one object simply contains or uses another object.
Virtual functions
#include <iostream>
#include <memory>
class Shape {
public:
virtual double area() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
explicit Circle(double radius) : radius(radius) {}
double area() const override { return 3.14159 * radius * radius; }
private:
double radius;
};
int main() {
std::unique_ptr<Shape> shape = std::make_unique<Circle>(2.0);
std::cout << shape->area() << '\n';
}Shape is an abstract class because it has a pure virtual function, written with = 0. A Circle supplies the implementation. The override keyword asks the compiler to verify that the function really overrides a virtual base function.
Runtime polymorphism
The pointer has static type Shape but refers to a Circle object. Calling the virtual area() function uses dynamic dispatch to select the implementation for the actual object type. This is runtime polymorphism.
Virtual destructors
If a class is designed to be used polymorphically and an object may be destroyed through a base pointer, the base class needs a virtual destructor. Without it, deleting a derived object through a base pointer can cause undefined behavior. The example uses virtual ~Shape() = default;.
Composition or inheritance?
Use inheritance for substitutable types that share an interface. Use composition when one type has another object as a member. Composition often gives simpler, more flexible designs and avoids fragile class hierarchies.
Common mistakes
- Forgetting
overrideand accidentally creating a different overload. - Deleting a derived object through a base pointer without a virtual destructor.
- Using inheritance only to reuse code when the is-a relationship is false.
- Making every function virtual without a design reason.
Practice
- Create an abstract
Employeeclass with a virtualpay()function. - Implement two derived employee types and call pay through a base reference.
- Explain why the base destructor is virtual.
- Model a Car that has an Engine using composition rather than inheritance.
Practice solutions
Show worked solutions
class Employee { public: virtual double pay() const = 0; virtual ~Employee() = default; };The pure virtual function makes Employee abstract; the virtual destructor supports safe destruction through a base pointer.- Derive salaried and hourly employee classes, override
pay() constin each, then pass an object to a function takingconst Employee&. Callingpay()through that reference dispatches to the derived override. - Deleting a derived object through a base pointer requires a virtual base destructor; otherwise the behavior is undefined. A virtual destructor ensures the derived destructor is invoked.
- Declare
Engine engine;as a data member ofCar. A car contains an engine; it is not a specialized kind of engine. Composition models this has-a relationship.
Check your understanding
What does virtual enable?
Answer: Runtime dispatch through a base pointer or reference, so the implementation belonging to the actual derived object can be called.
Summary
Use inheritance for substitutable types, virtual functions for runtime polymorphism, override for compiler checking, and a virtual destructor for polymorphic base classes that may be destroyed through a base pointer.