Are templates the best code reuse enabler in C++?
[12nd August 2007]
I've seen a number of posts being made recently about the reuse of code. I have to say this is something that frustrates me about C++; code reuse is just too hard, in general.
In this post I'm going to elaborate on why reuse is so difficult in the context of C++ in particular and hopefully offer the odd bit of advice to make things better.
The first major barrier to reuse is bad design. For example, Codeproject is rammed full of code where a particular algorithm is hardwired in to a GUI. It's as if some people think code is only useful if it comes with a user interface.
I can recall one recent example where some really quite useful code for parsing phone numbers (with all their braces and +'s and other quirks) was implanted in to a GUI. If the algorithm had been extracted in to an isolated component it could have been used in countless other applications, but instead you had to include the particular GUI widget advertised (which was only available to Windows .NET applications, I might add) every time you wanted to parse a phone number. One wouldn't dream of implementing a regular expression library in terms of a GUI text box widget, so why should this be any different?
By all means provide an example program that is driven by a user interface, but please don't impose the unnecessary dependency of a platform specific GUI library on potential clients.
This kind of practice isn't restricted to the web, of course. Dennis Forbes has argued that even reuse of any given company's code internally is often prohibitively difficult and makes the rather convincing point that most companies really don't need to fear code theft at all.
To elaborate, companies tend to write new code to depend on their own existing interfaces — or worse — their own existing implementations. The appropriate abstractions aren't used or they may not have been defined at all. The upshot of this is that modifying a little bit of code somewhere sends ripples of change surging through your code base, because the coupling o components is so strong.
Here's an example. There are plenty of C++ libraries out there for reading and writing images. But every single one of them, it seems, can only read and write rasters into and out of the library's own custom image object.
I have my own image type, tailored to my application, thank you very much. Why can't I use that? Of course I could read an image in to an instance of the library's proprietary container type and then copy the data out, pixel-by-pixel. But that's wasteful and just a little bit daft.
Of course, this is one of the reasons I designed jpegxx. It places no restriction on the image container you use and it's defined in terms of the iterator and stream abstractions, provided by the C++ standard library, which are available to everyone.
Quality of documentation
Sometimes I hear about a great library I'd like to use but when I investigate the code, I find that the documentation is either so incomplete, out-of-date or inconsistent that my time is better spent implementing my own library that does the same thing, than it is learning to use the library by trial and error.
I have to admit this happens rarely, but bigger companies are especially bad at providing good documentation, especially if their APIs are huge and complex.
The unit of reuse
Many would claim that object orientation provides the tools needed to write reusable code. But reusability isn't obtained automatically, just because you're using classes and virtual functions; you must explicitly design for reuse in object oriented code. If your abstractions and interfaces aren't right, you might as well not have bothered.
Throwing in the odd virtual function here or there is not enough to make your code reusable. I've seen a lot of code where it appears as if the author thought
this might be useful as a base class one day, so I had better make all these functions virtual.
This is completely the wrong approach. If you don't create your interface such that the client deriving from your class is unable to break your invariants by virtue of a correctly designed base class, you probably haven't thought about it hard enough. If the implementation of your base class changes, you could end up breaking classes derived from it.
So while object orientation can be used to create reusable components, it is very hard to do, especially in a statically typed language such as C++, where dependencies spread through code like wildfire. Sure, you might be able to reuse it here or there in your company's code base, but ask yourself if your components could be and would be reused by people in the outside world if they were to be made available. I'm betting that they probably wouldn't in the vast majority of cases.
Another barrier to reuse in the C++ arena is that of build systems. While many once believed that make was the one-true way, people are becoming more and more dissatisfied with make as a means to build their software.
This has caused some fragmentation. There are now numerous build systems available to build C++ code. To create a large application nowadays, you'll typically have to install 3 or 4 build systems just to compile the libraries you want to use in your application.
And good luck to you if you want the build systems to play nice with one another! You'll probably end up with an unmaintainable soup of build scripts.
More over, some build systems such as qmake, distributed with Qt are somewhat viral in nature. If you use Qt in your application, you pretty much have to use qmake to build all of your software for technical reasons I won't go in to here. This is very bad indeed for reuse. If I had two such libraries imposing their own distinct build systems, how could I possibly build my software!?
Now, I'm certainly one of the people that thinks make is obviously flawed and something better is needed. But no replacement has emerged as a clear winner, yet. So the current build systems climate is certainly hindering C++ code reuse.
Do C++ templates hold the key?
Perhaps somewhat controversially, I believe that templates provide the key — or at least the glue — that is needed to write re-usable code and compose existing libraries.
First of all, template code doesn't need compilation. It only exists in headers. All you need to do to re-use template code is
#include it in your source code.
Secondly and most importantly, templates allow and encourage generic programming. They are capable of integrating with existing classes and objects. Doug Gregor nicely explains why generic code enables reuse in this Google tech talk video on the concepts feature being added to the C++ standard.
For example, as I've already mentioned jpegxx allows you to read JPEG images in to an image object of your choice. All you have to do is take 2 minutes to create a little class that knows how to set a pixel's colour and you can use jpegxx with your own type of image.
This is all achieved using templates. Theoretically, you could write some OO code to do the same thing, making use of virtual functions. But this would be more complicated and less efficient and generally discourage reuse.
Template specialisation also helps with reuse. For example, when implementing the async library, I made sure that any functor could be called asynchronously by allowing the user to specialise the
async::functor_result_type<> template, in order to tell the library what the return type of the given functor is. Indeed without templates, async could not have been written.
I firmly believe that templates and the generic programming style they encourage are the basic building block of reusable code. It's no coincidence that some of the most highly regarded and reusable C++ libraries in existence are for the most part header-only libraries.
- often they won't need to as most well-behaved functors have a result_type typedef, anyway [↵]
All original content copyright© Edd Dawson.
Any opinions expressed by Edd are his own and are not necessarily shared by his employer. Or by anyone else, in fact.
All source code appearing on this website that was written by Edd Dawson is made available under the terms of the Boost software license version 1.0 unless otherwise stated or implied by the license associated with the work from which the code is derived.