C++ PROGRAMMING ยท 15 MIN STUDY

Abstract classes and virtual destructors

Build interfaces with pure virtual functions, understand abstract classes, and safely destroy derived objects through base-class pointers.

Learning objectives

  • Define a pure virtual function and an abstract class.
  • Use override to make virtual intent explicit.
  • Explain why a polymorphic base class usually needs a virtual destructor.
  • Understand safe deletion through a base-class pointer.

Prerequisites: inheritance, virtual functions, and smart-pointer ownership. Review Inheritance and polymorphism and RAII and smart pointers first.

Interfaces and pure virtual functions

A virtual function supports dynamic dispatch: the function selected at runtime can depend on the object's actual derived type. A pure virtual function is declared with = 0. A class with at least one pure virtual function is abstract and cannot be instantiated directly.

class Shape {
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};

Shape describes an interface: every concrete derived shape must provide an implementation of area().

Implement the interface

class Circle : public Shape {
    double radius;

public:
    explicit Circle(double r) : radius{r} {}

    double area() const override {
        return 3.141592653589793 * radius * radius;
    }
};

override asks the compiler to verify that the function really overrides a virtual base-class function. It catches spelling, parameter, and const-qualification mismatches.

Polymorphic use

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<Shape> shape =
        std::make_unique<Circle>(2.0);

    std::cout << shape->area() << '\n';
}

The pointer's static type is Shape, but the object is a Circle. The virtual call dispatches to Circle::area(). The smart pointer owns the derived object through the base pointer.

Why the base destructor must be virtual

When a derived object may be destroyed through a base-class pointer, the base class needs a virtual destructor. Without it, deleting the object through the base pointer has undefined behavior.

class Base {
public:
    virtual ~Base() = default;
};

A public virtual destructor supports polymorphic deletion. If a base class is not intended to be deleted through a base pointer, another design may be appropriate, such as a protected non-virtual destructor. For ordinary beginner polymorphic interfaces, a public virtual destructor is the safe default.

Virtual dispatch and object slicing

Polymorphism is usually used through pointers or references. Copying a derived object into a base object by value can cause object slicing: the derived portion is not part of the resulting base object.

Circle circle{2.0};
Shape& view = circle; // reference preserves the derived object
// Shape sliced = circle; // ERROR here because Shape is abstract

With a concrete base class, passing derived objects by base value can slice away derived state. Use a reference or pointer when runtime polymorphism is intended.

Common mistakes

  • Forgetting virtual on a base function intended for dynamic dispatch.
  • Omitting override, making signature mistakes harder to catch.
  • Using a public non-virtual base destructor with polymorphic deletion.
  • Trying to instantiate an abstract class.
  • Passing polymorphic objects by value and unintentionally slicing them.

Practice exercises

  1. What does virtual double area() const = 0; require derived concrete classes to do?
  2. Why is override useful?
  3. Why should Shape have a virtual destructor in the smart-pointer example?
  4. What is object slicing?

Solutions

Show exercise solutions
  1. They must provide an override for area() const before they can be instantiated.
  2. It makes the compiler check that the function overrides a virtual base function with a matching signature.
  3. The unique_ptr<Shape> ultimately destroys a Circle through a base pointer. A virtual destructor ensures correct derived destruction.
  4. It is the loss of derived-class state when a derived object is copied into a base object by value.

Summary

Abstract classes define interfaces using pure virtual functions. Use override for checked overrides. If objects can be destroyed polymorphically through base pointers, give the base class a virtual destructor.

References