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 for all monsters
class monster {
public:
	virtual ~monster();

	// forbid copying
	monster(monster const &) = delete;
	monster & operator=(monster const &) = delete;

	void receive_damage(double damage);
	void interact_with_chainsaw();
	std::string name() const;

protected:
	// allow construction for child classes only
	monster();

private:
	virtual void do_receive_damage(double damage) = 0;
	virtual void do_interact_with_chainsaw() = 0;
	virtual std::string do_name() const = 0;
};

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:

monster::monster()
{
}

monster::~monster()
{
}

void monster::receive_damage(double damage)
{
	do_receive_damage(damage);
}

void monster::interact_with_chainsaw()
{
	do_interact_with_chainsaw();
}

std::string monster::name() const
{
	return do_name();
}

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:

class hobgoblin : public monster
{
public:
	hobgoblin();
	virtual ~hobgoblin();

private:
	void do_receive_damage(double damage) final;
	void do_interact_with_chainsaw() final;
	std::string do_name() const final;

	double health_;
};

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:

hobgoblin::hobgoblin() :
	health_(100.0)
{
}

hobgoblin::~hobgoblin()
{
}

void hobgoblin::do_receive_damage(double damage)
{
	health -= damage;
}

void hobgoblin::do_interact_with_chainsaw()
{
	// imagine horrible, gory things here such as
	// having to deal with a singleton
}

std::string hobgoblin::do_name() const
{
	static std::string const name("Furry hobgoblin of nitwittery +5");
	return name;
}

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:

std::string taunt_monster(monster const & tauntee)
{
	return tauntee.name() + ", you are big and ugly and so is your mama!";
}

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…

 

18 Replies to “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).

    1. > “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 🙂

      1. Take boost.type_erasure for example, or std::function. They all use the assignment operator.

  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?

    1. 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.

      1. The destructor is implicitly marked noexcept(true), so you don’t need to specify it. And isn’t it the universal agreement that a destructor should never throw? Could you give an example situation where a throwing destuctor is considered appropriate?

  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

  8. Question about the copy constructor. Since this class contains pure virtual methods, is there any case where it would actually be called? Could we skip even defining it as delete?

  9. I have a question about the tension between NVI pattern and testability.
    Usually you need vitrural interface to make mocking possible, but NVI pattern advocate the idea that user shoudn’t need to know the virtualness of the interface. What’s your opinion on this?

  10. Great post! The only problem I have with is having private methods in the interface. Could you explain why it is appropriate to expose the private methods to potential clients?

    Thanks,
    Jason

Leave a Reply

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