Interfaces in C++

C++ has no such thing as an interface. It’s one of the few Java vs C++ comparisons that I think Java wins. Interfaces are useful. I’d like to talk about how we can get similar functionality into C++.

First we need to define our terms. Here’s an abstract base class in C++.

class Shape : public MyObjectLibraryBase {
  public:
    Shape() :
        mID(0) {}
    virtual ~Shape() {}

  public:
    virtual void draw(Canvas &) const = 0;

  private:
    int mID;
};

It’s a base class because it’s intended to be inherited from: it has virtual member functions. It’s abstract because at least one of its virtual member functions has been left undefined. In this case draw(). I’ve also assumed it to be part of some larger hierarchy and made Shape itself inherit from MyObjectLibraryBase, it doesn’t matter what for, it only matters that that’s what we want.

It should be clear why this has to be abstract – a Shape child class should be able to draw itself, but we don’t know what an instance of Shape would draw, it’s a placeholder in effect. Therefore we give it a draw() member that children will have to implement, but that Shape itself can’t. The two key outcomes of this then are:

  • We cannot instantiate an object of type Shape – it would be unable to draw() itself.
  • We can have a pointer to a Shape, which will actually be a pointer to a child class of Shape that does implement the draw() member function.

Now imagine that Canvas is not written by us, but is instead a third party library. It’s a library that maintains an object stack, and then renders them efficiently. Very quickly, our abstract base class becomes useless. If we’ve got a Canvas and a Shape can’t we just call Shape::draw(Canvas)? The answer is that it’s unlikely that that’s how a Canvas works; it’s more likely that the Canvas has some higher level interface for managing the objects under its control.

void someFunction(const Shape &S) {
    Canvas C;
    // Error, canvas knows nothing about Shape, so can't possibly
    // have an addToDrawStack(Shape) implementation.
    C.addToDrawStack(S);
}

The canvas here is maintaining a stack of objects to draw, maybe to maintain a z-ordering. Our shape knows nothing about that stack, it only knows how to draw itself on a Canvas, but we aren’t responsible for calling draw(), Canvas itself is – it knows whether the object is on screen or not, it knows the z-order. What do we do then? A bad solution is to inherit from something Canvas does know about:

class Shape : public MyObjectLibraryBase, public Canvas::Drawable {
  // ...

Yuck. Multiple inheritance. Avoid it (although, if I’m being fair, provided you only inherit interface-type abstract base classes, you are committing no crime). An impractical solution is to make Canvas understand Shape. It’s impractical because you can’t go modifying library code with application specific information whenever you like. It’s not library code then.

The solution is, as we’ll see, an interface.

C++ doesn’t have an interface; but here’s one from Java:

interface Drawable {
    void draw(Canvas C);
}

class Shape implements Drawable {
    // ...
    void draw(Canvas C);
}

Barring some syntactical differences; an interface is nothing more than a subset of an abstract base class. It is a subset because it meets these additional criteria:

  • All its members are functions; it has no member variables
  • All its members are public
  • All its members are abstract

The distinction between an abstract base class and an interface then is semantic more than anything. An interface is an abstract base class, but an abstract base class is not (necessarily) an interface. It would be unusual to have an interface hierarchy, whereas it is not unusual to have a class hierarchy.

A lot of articles out on the internet, when you search for “interface in C++” get this idea wrong; they talk only about abstraction. That is a different principle, used for defining common properties of a series of objects, and hiding the differences using polymorphism. An interface is exactly that: an interface to an object, probably to meet the needs of some other class library. If it helps, you can think of them as adapters: a way of making X fit into Y. The interface definition lets us adapt the abilities we have to fit requirements of otherwise unrelated classes. This is sometimes referred to as a contract.

A commonly used interface in Java, for example, is the Runnable. It’s a simple interface:

interface Runnable {
    void run();
}

In Java you might then have a job queue, a thread, a thread job queue, or whatever, defined in some third party library, that accepts a Runnable – you then add extends Runnable to your class definition and implement run(), and you get to use the simple interface to the library routines. I like to think of interface’s as the object oriented equivalent of callbacks with opaque pointers; only they’re far more flexible.

Fundamentally, we should think like this:

  • Inheritance is a noun relationship. If A inherits from B, then A is-a B (with B being a noun). e.g. a Dog is an Animal.
  • Interfaces are a verb relationship. If A implements interface B, then A can-do B (with B being a verb). e.g. a Dog can Bark.
  • Composition is an ownership relationship. If A contains a B, then A A has-a B. e.g. a Dog has a Leg.

It’s no coincidence that object methods are also a verb relationship; since an interface is a way of accessing those verbs.

How can we emulate interfaces in C++ then? The answer in C++ is almost always: another class.

// Interface.  No member variables, all public, all abstract.
// This would almost certainly be part of the Canvas library
class DrawableInterface {
  public:
    virtual void draw(Canvas &) const = 0;
};

// Our Shape definition, on object that we want to make drawable using
// the third-party Canvas.
class Shape {
  public:
    Shape() : mDrawable(*this) {}
    // Users of shape need this to be able to talk to Canvas
    DrawableInterface &dI() { return mDrawable; }

  protected:
    // We don't expect anyone but DrawableShape to be calling draw(), so
    // this should not be public
    virtual void draw(Canvas &C) const = 0;

  private:
    // This can be private because our child classes need never know
    // about it, and they will all implement the Shape abstract methods
    // that we're relying on.
    class DrawableShape : public DrawableInterface {
      public:
        // Note when we inherit from an interface class, it's pretty
        // much guaranteed that we will be making it concrete and be
        // adding data members; that's because we're _implementing_ the
        // interface, not creating a new one.
        DrawableShape(Shape& S) : mShape(S) {}
        // Our primary job is to redirect a DrawableInterface::draw()
        // call to a Shape::draw() call.
        void draw( Canvas &C ) const { mShape.draw(C); }
        Shape mShape;
    } &mDrawable;
};

It’s certainly not as pretty as the Java equivalent, but it’s got the job done. Our main base class Shape is now drawable without having to inherit anything from the Canvas library – the inheriting is done in our interface class, DrawableShape. The only concession we have to make is that when we pass our Shape to the Canvas library, we actually pass the return result of dI() rather than a Shape itself.

It should now be clear that we can “inherit” as many interfaces as we wish by adding more functions like dI() and their supporting interface classes. You can imagine we could give our shape a Printable interface, or a Renderable interface – all making our Shape compatible with various different libraries and all without them having to know anything about Shape.


Let me finish off with a complete example that I’ve pinched from a Rust implementation, and rewritten in C++ (the original article makes a good case for interfaces). Hopefully seeing it all together will make it clear how to use interfaces in C++.

int main()
{
    Log L;
    Book B;

    // Call L.makeWarmth() via an interface
    burn(L.burnable());
    // Call B.commitCrime() via an interface
    burn(B.burnable());

    return 0;
}

Log and Book are not derived from the same base class. burn() knows nothing about Logs or Books, and yet both can be burnt using it. When viewing the code, pay particular attention to the fact that neither Log nor Book implement any virtual functions, they do not inherit anything and the function that makes them “burn” is named differently in both cases – those attributes mean that the code that makes these objects usable with the library code has minimal impact on the code we write for our own purposes.

You can find the code in my cpp11 repository on github.


As an aside: remember that we can use C++’s conversion operator overloads to hide all trace that we’re even doing this; which makes the top-level code even more readable.

int main()
{
    Log L;
    Book B;

    // Call L.makeWarmth() via an interface
    burn(L);
    // Call B.commitCrime() via an interface
    burn(B);

    return 0;
}

The C++ compiler will notice that burn() requires an IBurnable but is being passed something else. It will (before issuing an error) try this instead:

    burn(static_cast<IBurnable>(L));

With no changes, this will fail too with a compile error; but if we implement a conversion operator in Log, suddenly it will work:

class Log
{
  public:
    Log() : iBurnable(*this) {}
    operator IBurnable &() { return iBurnable; }

  // ... etc ...
};

This is the particular advantage of C++ – we make intelligent objects that abstract away all the hard work, leaving us with really pleasant high-level interfaces.

This entry was posted in FussyLogic and tagged , , , , , . Bookmark the permalink. Trackbacks are closed, but you can post a comment.

Post a Comment

You must be logged in to post a comment.