Section 2 - Templatizing the Class

8A.2.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):

  1. We start the class prototype with the notation template<class T>.
  2. Inside the prototype, wherever we had a float, we put the symbol T.
  3. 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.)
  4. In the method definitions, any occurrence of the class name (except for the constructor and destructor name),  must have a <T> appended.
  5. 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;

  // put a default object into the static buffer
  staticBuff = T();

  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:

console shot

It's the same.

8A.2.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.

The use of the template in the client is particularly easy.  In fact, it looks very much like how we declared a vector class.  Of course.  The vector class is a template, just one that someone else designed for us.

trees from above