Interfaces done right

Interfaces are one of the backbones of modern, object-oriented design. You need to use instances of different classes which share similarities in a uniform way? Use interfaces. You cannot easily test a class because it depends on details of other classes? Make your class depend on interfaces instead and introduce mock objects. You have a class with a single responsibility, but a respectable amount of methods which are intended for different types of callers? Make your class implement several interfaces, and let different callers refer to the ones which suit their needs.

As you can see, interfaces used correctly are pretty powerful tools, and many programming languages such as Java and C# provide dedicated interface keywords. This is not the case for C++. This article explains how to write clean and safe interfaces for your classes.

Playmates for professional monster slayers

Imagine you work in the computer games business and your team sets out to create the next big role-playing game. Marketing tells you it should be set in a cyber-punk science fiction medieval sorcery universe overrun with zombie unicorns to accomodate as many tastes as possible. Besides the D&D license you are craving for, the first thing you need is monsters.

Monsters share a few commonalities. They have names and they take damage. Usually they even fight back, but in the spirit of agile development we will only implement features for the very easy difficulty mode in the first iteration. To express the commonalities of different kinds of monsters, we define the following monster interface:

Interface anatomy

Above snippet contains many details of note:

Virtual desctructor

Since we intend monster to be implemented by child classes, we need to make the destructor virtual and declare it explicitly. If we forget this, child objects may not be cleaned up correctly if they are destroyed via pointers to the monster base class.

Forbid copying

When a derived class is copied by a reference to the base class, additional members of the child class would not be copied. This effect is known as slicing and must be avoided, and thus we forbid copying by deleting both the copy constructor and the copy assignment operator. In pre-C++11 code, you would declare these functions  private and never implement them.

Side note: If you really need copying, implement a virtual  clone() method which returns a (smart) pointer to the base class.

Protected constructor

The constructor of monster has been made protected to highlight that this class can only be instantiated by child classes.

Public interface for end-users

A public interface is provided to all users of the class and its children. It consists of the methods receive_damage()name(), and interact_with_chainsaw(). This is cyberpunk, after all. The public interface should be safe to use and very difficult to use incorrectly. Give as many guarantees as you can.

Pure private interface for child class implementers

Finally, pure  virtual functions are provided which must be implemented by child classes. The purity is indicated by the  = 0 behind the prototypes. Please note that they are declared private. End users cannot call these functions directly, they can only use the  public interface. Child class implementers, on the other hand, must only implement the  private interface, i.e., the three methods do_receive_damage(), do_interact_with_chainsaw(), and do_name().

All in all, above declaration makes it very clear for users and implementers alike what can be expected of this class.

Interface implementation

Let us have a look at the implementation of the monster interface:

Methods from the  public interface call methods from the  private interface. This layer of indirection, however, can be put to great use: The  receive_damage() method could not only call do_receive_damage(), it could also perform additional tasks.

For example, it could check if the monster is still alive after being hit with a pointy stick. Furthermore, preconditions could be asserted, e.g., that the provided damage is a finite number in contrast to  NaN or  Inf (but you would use a type for that anyways, wouldn’t you?). Once corresponding code is written in the base class, all child classes benefit from this check, no matter how sloppy their implementers are. In turn, this also relieves end users of the  monster interface from the burden of performing those checks themselves. They do not have to trust all potential child classes to come, they can trust your interface.

This separation of  public end-user and  private implementer interfaces is so useful that it is known as the non-virtual interface (NVI) pattern and is widely used in modern C++ development.

Subclassing the interface

Let us now turn our attention to implementing a concrete monster class. The following snippet shows how the child class  hobgoblin is declared:

Since this is intended as a concrete class, we provide a public constructor for users to call. We also declare a destructor to clean up members of hobgoblin. Though it is not required to repeat the  virtual keyword, it highlights the destructor’s nature.

In order to make hobgoblin concrete, i.e., instantiable, we need to implement (and declare, obviously) all pure  virtual functions of the base class. Here we use two new C++11 features to protect us from past and future mistakes:

  • override tells the compiler to complain if no  virtual method with the exact same signature is present in any base class. This avoids accidently creating new methods with slightly different signatures such as missing consts.
  • final forbids child classes of  hobgoblin to provide their own implementations of these  virtual functions—a great feature for giving guarantees for this class and all its child classes. final implies override.
  • One could add the virtual keyword at the start of the declaration. However, it does not provide a great deal of new information, since override guarantees that this function has been declared virtual in a base class.

For completeness, this is how  hobgoblin would be implemented in a source file:

Dealing with monsters

To really benefit from the interface, you should use it everywhere to address concrete monsters, e.g., example in free-standing functions like this:

Concrete monster classes should only occur once in the (factory) code where concrete monster instances are created. Since copying monsters around is a problem, monsters are typically held in cages by smart pointers such as  std::shared_ptr<monster> or std::unique_ptr<monster>. Smart pointers guarantee that monsters are kept alive as long as they are needed.

Enjoy populating your dungeon. Do not forget to add the fight-back feature, though…

 

14 thoughts on “Interfaces done right

  1. Hi,

    it is a very nice article. I usually add one more layer between the interface and the concrete implementation: interfaces do only possesses pure virtual functions, and I have an abstract class which implements the interface and uses the template pattern with the do_* methods as you have explained.

    But, what I would really mention that there is no use to make your interface and even the concrete implementations noncopyable. Copying has only effects on the members of your classes and these members need to have the correct copy semantics. For example, if you have a class which needs to hold a pointer to some other interface use a shared_ptr or a unique_ptr. Both have different semantics. The shared_ptr can safely be copied; use it if you want to use the same instance of your interface in different classes. On the other hand the unique_ptr is non-copyable and so will be your class.

    If you need clone or value semantics one can use type erasure, which explicitly uses the assign operator, therefore, clone semantics come for free (without the need to implement a clone method).

    • > “If you need clone or value semantics one can use type erasure, which explicitly uses the assign operator”

      What technique/library are you referring to? Type erasure _might_ use the assignment operator. It depends on how you implement it.

      It would be nice to have the rest of the claim (“therefore, clone semantics come for free”) better founded :)

  2. Wouldn’t it make sense to have [~hobgoblin() = default;] … Since in modern c++, only resource-management classes should do anything in their dtor anyway, it probably would make sense to have just the base class provide a virtual dtor (and could we default that one too?) and have the others defaulted.

    Wouldn’t it?

    • Sorry for answering your comment this late. For non polymorphic classes I see no downside in having a default declared destructor. For polymorphic base classes it is more complicated thanks to exception specifications. Would we define virtual ~monster() = default; in the header file, monster would have a throw specification depending on wether any of its members destructors explicitly allow throwing or not. It is very likely to be noexcept since destructors should never ever throw. This would force all child implementations to declare their destructors as noexcept also. Is this a bad thing? Is this a good thing? I do not know yet. It could give an implementer of a child class a hard time, if he tries to figure out why his implementation does not compile yet (some of this compiler messages are not very comprehensive). On the other hand it makes sense to have destructors declared as noexecpt if they should never throw anyways. If I would do it, I would write virtual ~monster() noexecpt = default; In the base class. Making the noexcept explicit would give an implementer a hint, that his destructor needs to be declared as noexecpt also and it avoids looking like the noexcept specification happend on accident.

  3. This is a terrible example of how to use interfaces in C++. Also, while a hobgoblin is-a monster to us, they’re probably not monsters to other hobgoblins, so you’ve already used inheritance incorrectly. A better way to do this would be to split out the pure virtual methods into their own class, which would be your interface. Then use composition to add hobgoblin behaviors to a base monster class.

  4. Hi John!

    Thanks for the feedback. I do not think that it is a terrible example. Though I agree that one could think of scenarios in which hobgoblins interact with other hobgoblins (breeding, etc.), I mentioned that this is the first iteration. In this simple case, monsters do not need to know about each other (think turn-based battles in the classic Might & Magic games). All I care for is that from the perspective of the player character a hobgoblin behaves-like-a (better highlights the Liskov substitution principle) monster in that it takes damage. Other requirements yield different designs.

    Splitting the class is an interesting option and similar to what Karsten suggested earlier.

    Regards

    Michael

  5. For subclassing, it’s unnecessary to write both ‘override’ and ‘final’.
    ‘final’ already means that the class is virtual and will produce a compilation error if not (tested on Clang et GCC).

  6. Hi,

    This comment does not deal with this interesting post but I didn’t find out how
    to contact the authors of this blog.

    I think you wrote several interesting posts about Agile methods, C++11
    development etc. and I can see the last post was published 6 months ago.

    I hope this blog is not dead and I’m looking forward to further posts.

  7. Hi,
    Do you know any open source c++ projects that are done in a clean code manner? I am trying to do some c++ in my spare time and I found it really hard to find any c++ project with tdd and clean code.
    Thanks,
    g

Leave a Reply

Your email address will not be published. Required fields are marked *

*


seven − = 6

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">