Section 1 - Writing Our Own Templates
8A.1.1 Orientation

This week we learn how to create re-usable classes in the sense that the data they contain will be of a non-committed type. In other words, if the built-in stl templates do not meet our needs, we can write templates of our own. We will use a symbolic type parameter to fill in for the data type we wish to embed in the class, and only when a client instantiates a class will the data type be specified. Classes which work with these flexible type parameters are called class templates, or just templates.
We will do a couple short examples of templates in our first module, then in the second half of the week, we begin combining our many advanced tools, pushing the limits of our understanding of templates, inheritance and C++ tricks.
8A.1.2 A Plan for Templates
Before writing a class template we first write an ordinary class that uses, say, int data. This way, we can debug the methods to make sure they work. Once we are satsified, we replace the int data with a type parameter and thus make the class a true template.
Last lesson we learned a good way to create flexible arrays through the stl vector class. However, vectors did not protect against bad (out-of-bound) indices. We will try to solve this problem by designing our own special class called SafeArray. We would like to be able to create a SafeArray class once, and never have to reproduce it. The way we do so is through templates.
However, before creating a template we should always create the class using a concrete data type (like a float or int) as an example. Then we "templatize" the class to make it general. This allows us to get the bugs out of the class methods more easily.
So, without even knowing what a template is, let's start by defining a SafeArray of floats and hope that we can templatize it to apply to a SafeArray of any other data type.
By the way, this is different from inheritance. With inheritance we had to derive a class form a base class to make use of the base class's functionality. With templates, there are no classes involved. Just pure fun.
8A.1.3 A Class That Emulates an Array
We are trying to create a float array that is better than this:
float rainfall[5];
By better, I mean one that will not do bad things if we try to assign a number to rainfall[8], beyond the end of the array. The fact that we can overload the bracket operator, [], is going to make our solution a satisfying one. Here is the class prototype:
class SafeArray { static const long MAX_ARRAY_SIZE = 100000L; float *data; long size; public: SafeArray(long sz = 100, float initVal = 0); SafeArray(const SafeArray & rhs); ~SafeArray(); float &operator[](long index); SafeArray &operator=(const SafeArray & rhs); };
As you can see, the private data of the class consists of a float * which tells us that the array will be dynamically allocated. That means our destructor needs to do clean up, and we also should supply a copy constructor and overload the assignment operator.
8A.1.4 Reference Return Types
In what follows, the logic is straightforward. The only things that are worth mentioning are the return types of the brackets operator, [], and the assignment operator. Both have reference (&) return types. A reference return type enables a function to be on the LHS (left hand side) of an assignment operator. That's because we are returning the address of some variable, which means we can assign a value to it. Just remember to always use the & for return types of operator[] and operator=.
8A.1.5 SafeArray Implementation
Here is the code for the definitions of these member functions.
// SafeArray method definitions ------------------- SafeArray::SafeArray(long sz, float initVal) { if (sz < 1) size = 1; else if (sz > MAX_ARRAY_SIZE) size = MAX_ARRAY_SIZE; else size = sz; data = new float[size]; for (int k = 0; k < size; k++) data[k] = initVal; } SafeArray::SafeArray(const SafeArray& rhs) : data(NULL) { *this = rhs; } SafeArray::~SafeArray() { delete[] data; } float &SafeArray::operator [](long index) { static float staticBuff; // put a default object into the static buffer staticBuff = 0; if (index < 0 || index >= size) return staticBuff; else return data[index]; } SafeArray &SafeArray::operator=(const SafeArray & rhs) { // always check this if (this == &rhs) return (*this); delete[] data; // clear old data size = rhs.size; data = new float[size]; for (int k = 0; k < size; k++) data[k] = rhs.data[k]; return *this; }
This is not a complete class. I have simply provided the protection against bounds violations without a good error reporting mechanism. To make it more complete, we could include an error flag, error value, and/or exception. We keep it simple for now, if a bit unrealistic.
Besides the key aspect of this class -- the way in which the operator[] method protects against out-of-bounds errors, making the class absolutely safe -- this example demonstrates how a heap-based class handles its primary methods. Here are some highlights:
- The copy constructor must allocate memory based on the input parameter and copy the data to that new heap memory. However, the common way to do this is to call the assignment operator so as to avoid code duplication. We must be careful to initialize our data to NULL first, otherwise, the assignment operator call will die on delete[] data;.
- The assignment operator should allocate memory based on the input parameter and copy the data to that new heap memory. This allows one array to be assigned to another using the = and have the desired effect that all the element data will be copied (something that doesn't normally happen with arrays). We must always remember to first test for this == &rhs to avoid a client accidentally clobbering an object with a call like x = x;.
- The brackets operator returns a reference so that expressions like array[k] can appear on the LHS (left hand side) of the = sign. It must also supply a static object to return in case of an out-of-bounds error.
We'll test the array with this main():
int main() { int k; SafeArray rainfall(5); // assign data and go out-of-bounds for (k = -3; k < 10; k++) rainfall[k] = 17 + .03 * k; cout << "The array after some assignments:\n"; for (k = -3; k < 10; k++) cout << rainfall[k] << endl; // test the assignment operator SafeArray weather(40); weather = rainfall; cout << "Did weather[2] get assigned rainfall[2]?\n"; cout << weather[2] << " " << rainfall[2] << endl; return 0; }
which produces this output:

Our SmartArray class seems to work. Now that we have solved this problem for float arrays, we want to create a solution for all other data types. Enter class templates.