C++ Mixins - Reuse through inheritance is good... when done the right way
Conventional wisdom tells us that code reuse through inheritance is evil. Go ahead and Google it if you're not clear on the stance in the community. The majority of articles that I've read encourage the programmer to favour composition and delegation (for example, by using the strategy pattern) over inheritance in order to attain code reuse. In general this advice is good as code-reuse-through-inheritance (as it is commonly implemented) does suffer from many problems. However, composition-and-delegation suffers from its own set of problems... So is there a better approach?
In this article I introduce the concept of the C++ Mixin. It will be shown that code reuse through inheritance in C++ mixins is not evil - in fact, it has so many advantages that it should be a technique commonly employed by C++ programmers.
Tutorials on C++ mixins tend to be a bit confusing and use contrived examples that make you wonder why you'd bother with this obsure technique. The intention with this article is to provide a easy to understand introduction to Mixins, their implementation and a comparison to other techniques for code reuse.
Throughout this article a single example will be used. Consider that there is a Task Manager framework into which we can enqueue tasks for asynchronous execution. An interface for a task is defined as follows and must be implemented by all tasks that are to be executed by the framework:
It is expected that there will be some common bits of functionality that should be reused across task implementations. I have selected task execution timing and logging of start and completion messages to serve as examples.
Approach 1: Code reuse through inheritance (evil)
First we will take the approach of stick-the-reusable-stuff-in-a-base-class and see how we fare. Let's first attack the case of logging start and completion messages:
On the face of it, the above code looks reasonable - we have pushed the reusable code into the base class. The code is quite concise and simple to read. We can do the same thing for the task timing code:
In the second case, MyTask has reused task timing code instead of task logging code. Great! But there is a serious problem...
The problem occurs when we want to write a class that reuses both the timing and logging code - it quite simply can not be done and this shows up a major limitation of the stick-the-reusable-stuff-in-a-base-class approach. At some point you will discover that you can not combine the reusable parts in the way you want. What we would really like is an approach whereby we can reuse code in various combinations (like reuse both the logging and timing code in one class).
Another problem with this approach is the use of virtual functions. We have virtual functions calling virtual functions when we are trying to something relatively simple! It should be noted that the compiler can not generally inline virtual functions and there is some overhead in calling a virtual function compared to calling a non-virtual function. This runtime hit seems unreasonable, but how can we overcome it?
Approach 2: Code reuse through composition and delegation (still evil, just not as much)
We can rewrite the reusable logging and timing code to work with composition rather than inheritance. Let's look at the LoggingTask first:
In principal this is quite simple - the LoggingTask implements ITask and and is passed a pointer to an ITask in its constructor - let's call that the child task. The LoggingTask delegates its implementation of GetName() to the child task. It also delegates Execute() to the child task but performs logging before and after the delegation.
The timing task is implemented in a similar manner:
The above classes can be used in combination to add timing and logging to your task as follows:
While the principal may be simple, that looks a lot more complex than it was in our first example! There are some reasons for this:
The problems continue. Did you notice that we're forced to use heap allocations? Since the parent deletes the child, the child must be heap allocated. So, to add to the performance issues noted in the first approach, we now have heap allocations and runtime checks for null pointers. This seems like a whole lot of backward steps.
I shouldn't be so pessimistic; there are some positive aspects of this approach. It has overcome the issue of reusing both the logging and timing task in one class and it's quite straightforward to compose the reusable parts. The way that each part is decoupled is also very good - unlike the stick-the-reusable-stuff-in-a-base-class approach where the MyTask was tightly coupled to the reusable part through inheritance. Another positive is that each component (TimingTask, LoggingTask and MyTask) all directly implement the ITask interface. There is no special "OnExecute()" virtual method or other odd things going on.
With low coupling and high cohesion, we'd have to call this a good approach to attaining code reuse, right? Well, no - not in my book. I just can't get past it's flaws:
These problems make this approach still evil in my mind. Let's try to find a technique that overcomes these problems without introducing problems like we saw in the stick-the-reusable-stuff-in-a-base-class approach.
Approach 3: Code reuse through inheritance - revisted (Clayton's reuse)
In this approach we turn the idea of stick-the-reusable-stuff-in-a-base-class on its head and instead stick-the-reusable-stuff-in-a-derived-class. I call this Clayton's reuse; the reuse you have when you're not having reuse. While this section might seem silly, it's a stepping stone to understanding C++ mixins, so please bear with me.
Consider our simple task that we've used as an example so far:
We can add timing to this class by creating a subclass:
And we can add logging to the TimingTask:
Ok, so what have we achieved? The logging code is coupled to the timing code which is coupled to MyTask and none of it is particularly reusable. So what's the point?
The point is that other than the coupling and lack of reuse, we have overcome some problems:
With all these positives, this is worth pursuing - all we need to do is work out how to decouple the classes and allow for reuse. It turns out that this silly looking example is just one small step away from greatness!
Approach 4: Code reuse through mixins
Although Mixins have a fairly broad definition, I think it's quite well understood that in C++ circles a Mixin refers to the following idiom - a template class that is parameterised on its base class:
If this is the first time you've seen this construct, you probably need to pause and think about what this means for a moment... A class's base class is supplied by a template parameter... A template class that has a different base class for each template instantiation... It turns out that this is the key to fixing the problems in our Clayton's reuse example!
Let's revist the LoggingTask:
The problem with the LoggingTask is that it is coupled to the TimingTask even though logging and timing are orthogonal concepts. The decoupling is simple - we just supply the base class of the LoggingTask as a template parameter:
Hey, that's a Mixin!
So long as type T provides an Execute() method (as LoggingTask calls T::Execute()) then this code will compile and run as expected. Let's rewrite our example using mixins:
In the above, the TimingTask and the LoggingTask are written as Mixins while MyTask is not. This is to be expected as MyTask is not reusable and represents a concrete concept rather than the abstract concepts embodied in the Mixins.
Building up a class using the Mixins is quite easy. Some examples are shown below:
But hang on a minute, none of this helps us plug into our Task Manager framework as the classes do not implement the ITask interface. This is where one final Mixin helps - a Mixin which introduces the ITask interface into the inheritance hierarchy, acting as an adapter between some type T and the ITask interface:
Using the TaskAdapter is simple - it's just another link in the chain of mixins.
Looking back at the problems with the previous approaches, it should be clear that Mixins avoid the problems of Composition and Delegation while preserving the advantages of Clayton's reuse and, once you understand the pattern, looks as simple as stick-it-in-the-base-class reuse.
The Visual Studio 2008 solution attached to this post contains all of the examples from this article.