Overview of templates
Now a problem arises. You have an
IntStack, which holds integers. But you want a stack that holds shapes or
aircraft 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 in this situation: the C way, presented here for contrast; the
Smalltalk approach, which significantly affected C++; and the C++ approach:
templates.
The C solution. Of course
you’re trying to get away from the C approach because it’s messy and
error prone and completely inelegant. In this approach, 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 solution. Smalltalk
(and Java, following its example)
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 (similar to the example at the end of Chapter 15). But because the
library in Smalltalk is of such fundamental importance, 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 (and
Java[59])
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, which
is also the case in Java) is referred to as an “object-based
hierarchy.” You may have heard this term and assumed it was some new
fundamental concept in OOP, like polymorphism. It simply refers to a class
hierarchy 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 did C++, and because 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 an early C++
implementation[60],
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 (and
most other OOP languages that I know of), all classes are automatically derived
from a single hierarchy, but this isn’t true in C++. You might have your
nice object-based hierarchy with its container classes, but then you might buy a
set of shape classes or aircraft classes from another vendor who didn’t
use that hierarchy. (For one thing, using that hierarchy imposes overhead, which
C programmers eschew.) How do you insert 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 (a similar example
was given at the end of Chapter 15):
Now OShape has
Shape’s characteristics and behaviors, but because it is also
derived from Object it can be placed in Container. The extra
inheritance into OCircle, OSquare, etc. is necessary so that those
classes can be upcast into OShape and thus retain the correct behavior.
You can see that things are rapidly getting messy.
Compiler vendors invented and included
their own 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 Volume
2 of this book that its complexity is best avoided except in special
cases.