Now
a problem arises. You have an
IStack,
which holds integers. But you want a stack that holds shapes or airliners or
plants or something else. Reinventing your source-code every time doesn’t
seem like a very intelligent approach with a language that touts reusability.
There must be a better way.
There
are three techniques for source-code reuse: the C way, presented here for
contrast; the Smalltalk approach, which significantly affected C++; and the C++
approach: templates.
The
C approach
Of
course you’re trying to get away from the C approach because it’s
messy and error prone and completely inelegant. You copy the source code for a
Stack
and make modifications by hand, introducing new errors in the process. This is
certainly not a very productive technique.
The
Smalltalk approach
Smalltalk
took
a simple and straightforward approach: You want to reuse code, so use
inheritance. To implement this, each container class holds items of the generic
base class
object.
But, as mentioned before, the library in Smalltalk is of fundamental
importance, so fundamental, in fact, that you don’t ever create a class
from scratch. Instead, you must always inherit it from an existing class. You
find a class as close as possible to the one you want, inherit from it, and
make a few changes. Obviously this is a benefit because it minimizes your
effort (and explains why you spend a lot of time learning the class library
before becoming an effective Smalltalk programmer).
But
it also means that all classes in Smalltalk end up being part of a single
inheritance tree. You must inherit from a branch of this tree when creating a
new class. Most of the tree is already there (it’s the Smalltalk class
library), and at the root of the tree is a class called
object
– the same class that each Smalltalk container holds.
This
is a neat trick because it means that every class in the Smalltalk class
hierarchy is derived from
object,
so every class can be held in every container, including that container itself.
This type of single-tree hierarchy based on a fundamental generic type (often
named
object)
is referred to as an “object-based hierarchy.”
You may have heard this term before and assumed it was some new fundamental
concept in OOP, like polymorphism. It just means a class tree with
object
(or some similar name) at its root and container classes that hold
object.
Because
the Smalltalk class library had a much longer history and experience behind it
than C++, and the original C++ compilers had
no
container class libraries, it seemed like a good idea to duplicate the
Smalltalk library in C++. This was done as an experiment with a very early C++
implementation,
[37]
and because it represented a significant body of code, many people began using
it. In the process of trying to use the container classes, they discovered a
problem.
The
problem was that in Smalltalk, you could force people to derive everything from
a single hierarchy, but in C++ you can’t. You might have your nice
object-based hierarchy with its container classes, but then you might buy a set
of shape classes or airline classes from another vendor who didn’t use
that hierarchy. (For one thing, the hierarchy imposes overhead, which C
programmers eschew.) How do you shoehorn a separate class tree into the
container class in your object-based hierarchy? Here’s what the problem
looks like:
Because
C++ supports multiple independent hierarchies, Smalltalk’s object-based
hierarchy does not work so well.
The
solution seemed obvious. If you can have many inheritance hierarchies, then you
should be able to inherit from more than one class: Multiple inheritance will
solve the problem. So you do the following:
Now
oshape
has
shape’s
characteristics and behaviors, but because it is also derived from
object
it can be placed in
container.
But
multiple inheritance wasn’t originally part of C++. Once the container
problem was seen, there came a great deal of pressure to add the feature. Other
programmers felt (and still feel) multiple inheritance wasn’t a good idea
and that it adds unneeded complexity to the language. An oft-repeated statement
at that time was, “C++ is not Smalltalk,” which, if you knew enough
to translate it, meant “Don’t use object based hierarchies for
container classes.” But in the end
[38]
the pressure persisted, and multiple inheritance was added to the language.
Compiler vendors followed suit by including object-based container-class
hierarchies, most of which have since been replaced by template versions. You
can argue that multiple inheritance is needed for solving general programming
problems, but you’ll see in the next chapter that its complexity is best
avoided except in certain cases.
The
template approach
Although
an object-based hierarchy with multiple inheritance is conceptually
straightforward, it turns out to be painful to use. In his original book
[39]
Stroustrup demonstrated what he considered a preferable alternative to the
object-based hierarchy. Container classes were created as large preprocessor
macros
with arguments that could be substituted for your desired type. When you wanted
to create a container to hold a particular type, you made a couple of macro
calls.
Unfortunately,
this approach was confused by all the existing Smalltalk literature, and it was
a bit unwieldy. Basically, nobody got it.
In
the meantime, Stroustrup and the C++ team at Bell Labs had modified his
original macro approach, simplifying it and moving it from the domain of the
preprocessor into the compiler itself. This new code-substitution device is
called a
template[40],
and it represents a completely different way to reuse code: Instead of reusing
object code, as with inheritance and composition, a template reuses
source
code.
The container no longer holds a generic base class called
object,
but instead an unspecified parameter. When you use a template, the parameter is
substituted
by
the compiler
,
much like the old macro approach, but cleaner and easier to use.
Now,
instead of worrying about inheritance or composition when you want to use a
container class, you take the template version of the container and stamp out a
specific version for your particular problem, like this:
The
compiler does the work for you, and you end up with exactly the container you
need to do your job, rather than an unwieldy inheritance hierarchy. In C++, the
template implements the concept of a
parameterized
type
.
Another benefit of the template approach is that the novice programmer who may
be unfamiliar or uncomfortable with inheritance can still use canned container
classes right away.
[37]
The OOPS library, by Keith Gorlen while he was at NIH. Generally available from
public sources.
[38]
We’ll probably never know the full story because control of the language
was still within AT&T at the time.
[39]The
C++ Programming Language
by Bjarne Stroustrup (1st edition, Addison-Wesley, 1986).
[40]
The inspiration for templates appears to be ADA generics.