The Need for
Templates
Imagine a simple bounds-checked array class:
#define MAX_ELEMS 8
class Array {
public:
long& operator[](size_t n) {
if( n < 0 || n >= MAX_ELEMS ) throw "out of bounds!";
return m_rg[n];
}
protected:
long m_rg[MAX_ELEMS];
};
This class makes quiet, hard-to-find errors loud
and easy to find:
void main(int argc, char* argv[]) {
long rg[8]; // Built in array type
rg[8] = 1; // will corrupt the stack, but quietly
Array array; // Bounds-checked array type
array[8] = 1; // will complain loudly
}
The bounds-checking part of the Array
class really has nothing to do with the data being managed; using a
little bit of C++ trickery, this is even easier to spot:
typedef long T;
class Array {
public:
T& operator[](size_t n) {
if( n < 0 || n >= MAX_ELEMS ) throw "out of bounds!";
return m_rg[n];
}
protected:
T m_rg[MAX_ELEMS];
};
Notice that we've replaced the use of
long with a generic type T. Unfortunately, this
trick doesn't allow us to reuse the Array class with
different types of T. When the compiler sees the
Array class, it won't let us change T and compile
it again with another type T. To get any reuse out of the
Array class as it is, we have to do some cut-and-paste
work and create different Array classes, one for each type
we're interested in managing:
class ArrayOfChar {
public:
char& operator[](size_t n);
protected:
char m_rg[MAX_ELEMS];
};
class ArrayOfLong {
public:
long& operator[](size_t n);
protected:
long m_rg[MAX_ELEMS];
};
Besides the tedium involved with this technique,
the developer of the Array family of classes would have to
build an Array class for every type that the user of the
class would want to manage. Because some of these types can be
defined after the Array
class is built, this is an especially difficult task. We'd like the
compiler to step in and help us. And so it will, with
templates.
|