Section 7 - Review of Class Templates, Continued
1B.7.1 Creating a Template Class
To make this class applicable to any data type, not just floats, we must add some notation. It goes as follows (don't try to memorize these, just look briefly at them, and refer back when you see the example):
- We start the class prototype with the notation template<class T>.
- Inside the prototype, wherever we had a float, we put the symbol T.
- In the method definitions, if they are defined outside the class prototype, as is commonly done, we begin each one with the notation template <class T>. (You can skip this step for any method definitions presented in-line.)
- In the method definitions, any occurrence of the class name (except for the constructor and destructor name), must have a <T> appended.
- Any float literals (like 1.5 or 0) must be replaced by an appropriate object or object instantiation (like T()). (See default constructor prototype.)
In the client, instead of declaring a SafeArray, we declare a SafeArray<float> or SafeArray<string> or SafeArray<iTunesEntry>, depending on the type we need. We'll declare a SafeArray<float> just to see if our program still works after all these changes.
Here is the full program with the templatized class and an adjusted main():
#include <iostream> #include <string> using namespace std; template<class T> class SafeArray { static const long MAX_ARRAY_SIZE = 100000L; T *data; long size; public: SafeArray(long sz = 100, T initVal = T() ); SafeArray(const SafeArray & rhs); ~SafeArray(); T &operator[](long index); SafeArray &operator=(const SafeArray & rhs); }; int main() { int k; SafeArray<float> 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<float> weather(40); weather = rainfall; cout << "Did weather[2] get assigned rainfall[2]?\n"; cout << weather[2] << " " << rainfall[2] << endl; return 0; } // SafeArray method definitions ------------------- template <class T> SafeArray<T>::SafeArray(long sz, T initVal) { if (sz < 1) size = 1; else if (sz > MAX_ARRAY_SIZE) size = MAX_ARRAY_SIZE; else size = sz; data = new T[size]; for (int k = 0; k < size; k++) data[k] = initVal; } template <class T> SafeArray<T>::SafeArray(const SafeArray<T>& rhs) : data(NULL) { *this = rhs; } template <class T> SafeArray<T>::~SafeArray() { delete[] data; } template <class T> T &SafeArray<T>::operator [](long index) { static T staticBuff; if (index < 0 || index >= size) return staticBuff; else return data[index]; } template <class T> SafeArray<T> &SafeArray<T>::operator=(const SafeArray<T> & rhs) { // always check this if (this == &rhs) return (*this); delete[] data; // clear old data size = rhs.size; data = new T[size]; for (int k=0; k < size; k++) data[k] = rhs.data[k]; return *this; }
The output is:
It's the same.
1B.7.2 Analysis
Besides the odd notation, the concept is simple. We use the symbol "T" in place of the type float in various parts of the method definitions. It is a place holder. In fact, T can be anything, as long as you use the same letter or word in the phrase template<class T>. You have to replace the T with Object or Comparable everywhere. Most people just use T.
Whenever we use a default value, like 0, where we expected floats, we also have to replace that with something compatible with our T class. Since we don't know anything about it, we use the automatic anonymous construction, T(), instead of 0, since all classes (usually) have a default constructor:
SafeArray(long sz = 100, T initVal = T() );
The use of the template in the client is particularly easy. In fact, it looks very much like our use of the vector class. Of course. The vector class is a template, just one that someone else designed for us.
