Abstract classes and interfaces

Introduction

Sometimes a base class cannot sensibly do the job itself: what is the area of a "shape" in general? Declare the function pure virtual with = 0: it has no body, and every concrete derived class must override it.

Some base classes exist only to describe what every derived class must do. Making them abstract lets the compiler enforce that, so that no one forgets to supply a part.

How it works

Pure virtual functions

#include <iostream>
#include <string>
using namespace std;

class Shape {
public:
    virtual double area() const = 0;
    virtual string name() const = 0;
    void describe() const {
        cout << name() << " with area " << area() << endl;
    }
    virtual ~Shape() {}
};

class Rectangle : public Shape {
    double w, h;
public:
    Rectangle(double w, double h) : w(w), h(h) {}
    double area() const override {
        return w * h;
    }
    string name() const override {
        return "rectangle";
    }
};

int main() {
    Rectangle r(2, 3);
    Shape& s = r;
    s.describe();
    return 0;
}
Open in compiler
  • virtual double area() const = 0; makes Shape an abstract class. You cannot create a Shape object: Shape s; is a compile error. You can still have Shape * and Shape &, pointing to derived objects.
  • Rectangle overrides every pure virtual function, so it is concrete: it can be created.
  • Shape::describe is an ordinary member function that calls the pure virtual area(). Each derived class fills in the missing piece. This pattern (fixed steps in the base class, details in derived classes) is called the template method.

Syntax

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

Example

#include <iostream>
#include <string>
using namespace std;

class Printable {
public:
    virtual void print() const = 0;
    virtual ~Printable() {}
};

class Document {
protected:
    string title;
public:
    Document(string t) : title(t) {}
};

class Report : public Document, public Printable {
    int pages;
public:
    Report(string t, int p) : Document(t), pages(p) {}
    void print() const override {
        cout << "Report: " << title << " (" << pages << " pages)" << endl;
    }
};

void printTwice(const Printable& p) {
    p.print();
    p.print();
}

int main() {
    Report r("Lab results", 4);
    printTwice(r);
    return 0;
}
Open in compiler

A class with only pure virtual functions (and a virtual destructor) is an interface: a list of what an object can do, with no data and no code. Printable says "this can print itself". A class can inherit from a base class and from interfaces: Report is a Document and is also Printable, Saveable. Code that only needs printing takes a Printable & and works with any class that keeps that promise.

Line by line

  1. Printable is an interface: one pure virtual function, print, and a virtual destructor.
  2. Document is an ordinary base class that holds a title.
  3. Report inherits from both, so it is a Document and it is Printable.
  4. Report overrides print, so it is concrete and can be created.
  5. printTwice takes any const Printable&, so it works with a Report without knowing anything else about it.
  6. main makes one report and prints it twice through the interface.

Expected result

Report: Lab results (4 pages)
Report: Lab results (4 pages)

In the real world

Java and C# have a separate interface keyword for this idea. In C++, pure virtual classes play the same role in database drivers, audio plug-ins and cross-platform libraries, where each system supplies its own concrete class.

Common mistakes

  • Forgetting to override one pure virtual function: the derived class is still abstract, and creating it fails with a message listing what is missing.
  • Calling a pure virtual function from the base class's constructor: the derived part does not exist yet.
  • Forgetting the virtual destructor in an interface, so deleting through it skips the derived destructor.

Best practices

  • Keep interfaces small, with one clear ability each.
  • Give abstract classes a virtual destructor.
  • Write functions that take the interface, not a concrete class, when that is all they need.

Summary

A pure virtual function makes a class abstract, so it cannot be created, while derived classes that override everything are concrete. A class of pure virtual functions is an interface, and a class may implement several.

Quick check

1. What makes a class abstract?
2. Shape is abstract. Which is allowed?
3. A derived class overrides only one of two pure virtual functions. It is:

Key takeaways

  • You cannot create an object of an abstract class.
  • Pointers and references to an abstract class are allowed.
  • An interface describes abilities, and a class can have several.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.