Concepts / Interfaces and Abstract Base Contracts

Interfaces and Abstract Base Contracts

An abstract class is not meant to be instantiated on its own. It exists to serve as a base for other classes.

  • Programming

A Drawing Program with Many Kinds of Figures

Imagine you are building a drawing program. Users can place circles, squares, and triangles on a canvas, and the program needs to keep all of them in one collection. Each figure is different, yet the program must process every item in that collection. The natural question is how to write a single function that handles every item without writing a separate function for each kind of figure.

What do you think happens?

Before reading on, predict: if Shape is the common base of Circle and Square, should the program be allowed to create a plain Shape object directly, such as a generic shape sitting on the canvas?

  • Yes, a generic Shape object is useful for the canvas

  • No, every real figure is a particular kind of shape

Reveal answer
Answer:

No. There is no such thing as a generic shape sitting in a drawing. Every real shape is a particular kind of shape, such as a circle or a square.

This is why Shape is designed as an abstract class. Its job is to name the common idea of a shape and to serve as the base that specific figures build on, not to be created on its own.

Tracing a Loop Over Shape Pointers

The collection cannot hold the figures themselves as one uniform kind of value, because a circle and a square are different kinds of things. Instead, the collection holds pointers to shapes. Each pointer refers to some concrete figure, whether that figure is a circle, a square, or a triangle. The collection is uniform even though the figures are not: every element is a pointer to a Shape.

drawAll loopwalks the collectionshapes collectionall elements are ShapepointersCircle objectruns Circle draw()Square objectruns Square draw()1. fetch next pointer2. pointer leads to a circle3. next pointer leads to a square
When the general function loops over the collection and calls draw() on each element, which concrete draw() runs for each pointer?

Trace the diagram step by step. The loop takes one pointer from the collection. That pointer refers to some concrete figure, so the call to draw() is made through the Shape interface. The program does not need to know in advance whether the pointer points to a circle or a square. It only needs to know that the pointer leads to a Shape and that every Shape supports the operations the abstract class promises. The choice of which draw() runs is made for each element as the loop reaches it.

cpp
Walk through it
  1. 1
    Shape declares draw() as a contract without giving it a body.
  2. 2
    Circle and Square each inherit from Shape and complete the contract with their own draw().
  3. 3
    drawAll accepts a vector of Shape pointers and never names Circle or Square.
  4. 4
    Each s->draw() call is resolved to the concrete figure that the pointer refers to.
Notes on the code
  • The = 0 marks draw() as a promise that every derived class must complete
  • Circle and Square each bring their own stored fields
  • drawAll works for any collection of Shape pointers, including figures added later

What Makes a Class Abstract

Definition

An abstract class is not meant to be instantiated on its own. It exists to serve as a base for other classes. Shape is the standard example: it names the shared idea of a shape, and specific figures such as circles and squares are the classes that are actually used as objects.

The word contract is useful here. The abstract base states what every shape can do, and the derived classes must honor that statement. A function that receives a Shape pointer relies on this promise. It does not need to know how the promise is kept for a particular figure, only that the promise is kept. This is why the abstract base can be described as a shared interface rather than a shared implementation.

Shapeabstract: names the sharedidea, not created directlyCircleits own details, completesdraw()Squareits own details, completesdraw()Triangleits own details, completesdraw()is a kind ofis a kind ofis a kind of
How are Shape, Circle, and Square related, and what does the abstract base contribute compared with the specific figures?

The diagram shows the relationship between kinds. It does not show the fields inside each figure. The abstract base sits above the derived classes as the shared name and the shared contract, while the details stay specific to each kind. This is the reason an abstract class cannot be created on its own: a shape that is not a particular kind of shape has no concrete details to describe it.

Why Shape Stores Almost Nothing in Common

It is tempting to think of a base class as a template that fills in fields for every subclass. A circle has a radius, a square has a side length, and a triangle has three sides, so one might expect a common base to hold a shared set of numbers. The abstract class idea runs the other way. The source states the point directly: as far as representation is concerned, nothing except the location of the pointer to the vtbl is common for every Shape. The Shape part of each object carries no common fields that all shapes must store in the same way.

vtbl pointershared: location of thepointer to the vtblvtbl pointershared: location of thepointer to the vtblradiusspecific to Circlewidthspecific to Rectangleheightspecific to Rectangle
What does a Circle object look like compared with a Rectangle object, and which part is shared because both inherit from Shape?

Read the two columns as the memory layouts of a Circle and a Rectangle. The only part that both objects share in this contract is the vtbl pointer, the location that leads to the table of virtual functions. Everything else, such as the radius of a circle or the width and height of a rectangle, belongs to the specific figure. The base therefore provides a shared interface rather than shared stored data. What the derived classes have in common is the way they are used, not a common layout of values.

Because of this design, one general function can manipulate a vector of pointers to shapes, whatever concrete figures those pointers refer to. The function does not depend on the stored fields of any figure. It depends only on the operations promised by the abstract class.

Mistakes Beginners Make with Abstract Bases

  • Common mistake

    Assuming a base class must hold the shared data for all its subclasses

    What it looks like

    Putting a radius field in Shape because circles have one, then expecting squares to use it too

    Why it fails

    The source states that nothing except the location of the pointer to the vtbl is common for every Shape. Each figure's details differ too much to share one layout.

    Do this instead

    Use the abstract base to name the shared operations, and keep the stored details in each derived class.

  • Common mistake

    Trying to create a Shape object directly so that it can be placed on the canvas

    What it looks like

    Writing a plain Shape object as a stand-in for a figure the user has not chosen yet

    Why it fails

    An abstract class is not meant to be instantiated on its own. A generic shape is not a real figure in a drawing.

    Do this instead

    Create a concrete figure such as a Circle or a Square and store a pointer to it in the collection.

  • Common mistake

    Writing a separate function for each kind of figure in the collection

    What it looks like

    One function for drawing circles, another for drawing squares, and another for triangles, all over the same canvas

    Why it fails

    This repeats the work and ignores the purpose of the shared base, which lets one general function handle any Shape pointer.

    Do this instead

    Write one function that takes a collection of Shape pointers and calls the shared operation on each element.

Practice: Tracing the Shape Contract

MEDIUM

A drawing program stores a collection of pointers that refer to a Circle, a Square, and a Triangle, in that order. A general function loops over the collection and calls draw() on each element. Explain, for each element, which kind of object the call reaches and what information the function needed in order to make the call. Then explain why the function would still work if a fourth kind of figure were added later.

Hints
  • Look at the collection: every element is a pointer to a Shape, not a Circle or Square.
  • Think about what the abstract base promises, and what the function does not need to know.
  • Consider the phrase: the function only needs to know that each pointer leads to a Shape.

Reviewing the Contract Idea

  1. 1
    An abstract class is not meant to be instantiated on its own. It exists to serve as a base for other classes, such as Shape for circles and squares.
  2. 2
    Shape provides a shared interface rather than shared stored data: as far as representation is concerned, only the location of the pointer to the vtbl is common for every Shape.
  3. 3
    A collection holds pointers to shapes, so every element has the same type even though the figures differ.
  4. 4
    One general function can loop over Shape pointers and call the promised operations, without knowing which concrete figure each pointer refers to.

Key Takeaways

  • An abstract class is not meant to be instantiated on its own; it exists to serve as a base for other classes.
  • Shape is the standard example, because every real figure is a particular kind of shape.
  • Shape provides a shared interface, not shared data: only the location of the pointer to the vtbl is common for every Shape.
  • A collection of Shape pointers lets one general function process any figure without a separate function for each kind.

Related concepts

More concepts starting with I