Templates are another useful but dangerous concept in C++. With templates, you can parameterize a class definition with a type, to allow you to write generic type-independent code. For example, our Stack implementation above only worked for pushing and popping integers; what if we wanted a stack of characters, or floats, or pointers, or some arbitrary data structure?
In C++, this is pretty easy to do using templates:
template <class T>
class Stack {
public:
Stack(int sz); // Constructor: initialize variables, allocate space.
~Stack(); // Destructor: deallocate space allocated above.
void Push(T value); // Push an integer, checking for overflow.
bool Full(); // Returns TRUE if the stack is full, FALSE otherwise.
private:
int size; // The maximum capacity of the stack.
int top; // Index of the lowest unused position.
T *stack; // A pointer to an array that holds the contents.
};
To define a template, we prepend the keyword template to the class definition, and we put the parameterized type for the template in angle brackets. If we need to parameterize the implementation with two or more types, it works just like an argument list: template <class T, class S>. We can use the type parameters elsewhere in the definition, just like they were normal types.
When we provide the implementation for each of the member functions in the class, we also have to declare them as templates, and again, once we do that, we can use the type parameters just like normal types:
// template version of Stack::Stack
template <class T>
Stack<T>::Stack(int sz) {
size = sz;
top = 0;
stack = new T[size]; // Let's get an array of type T
}
// template version of Stack::Push
template <class T>
void
Stack<T>::Push(T value) {
ASSERT(!Full());
stack[top++] = value;
}
Creating an object of a template class is similar to creating a normal object:
void
test() {
Stack<int> s1(17);
Stack<char> *s2 = new Stack<char>(23);
s1.Push(5);
s2->Push('z');
delete s2;
}
Everything operates as if we defined two classes, one called Stack<int> -- a stack of integers, and one called Stack<char> -- a stack of characters. s1 behaves just like an instance of the first; s2 behaves just like an instance of the second. In fact, that is exactly how templates are typically implemented -- you get a complete copy of the code for the template for each different instantiated type. In the above example, we'd get one copy of the code for ints and one copy for chars.
So what's wrong with templates? You've all been taught to make your code modular so that it can be re-usable, so everything should be a template, right? Wrong.
The principal problem with templates is that they can be very difficult to debug -- templates are easy to use if they work, but finding a bug in them can be difficult. In part this is because current generation C++ debuggers don't really understand templates very well. Nevertheless, it is easier to debug a template than two nearly identical implementations that differ only in their types.
So the best advice is -- don't make a class into a template unless there really is a near term use for the template. And if you do need to implement a template, implement and debug a non-template version first. Once that is working, it won't be hard to convert it to a template. Then all you have to worry about code explosion -- e.g., your program's object code is now megabytes because of the 15 copies of the hash table/list/... routines, one for each kind of thing you want to put in a hash table/list/... (Remember, you have an unhelpful compiler!)