Notes for week 9

For this week's links click here.

What Makes a Good Template Class

Template classes are created when you have a container class that can provide a service that transcends the data type stored in it. Arrays, for example, may be used to store any data object. In addition the functions should be applicable to a large number of data types. In our array class, we are implementing a few simple operations that have a wide audience. Even if people ask for more functions, these functions are easily implemented by the user in a descendent class. Do not clutter your template classes with too many functions.

Constructing a Template Class - Array

While the primitive c array creates many memory management problems in c++, it will still be the basis for our array class. We will face the memory management problems in this class so we can avoid them elsewhere. If this class is properly constructed, it will eliminate the memory management problems for its users. To see an example of such memory management problems click here

In c++ it is possible to write your own memory manager from scratch. My experience with this has left me feeling that except in rare cases, it is best to base classes on the existing Borland memory management. We will therefore use both new and delete in our template functions to allocate and release memory. This is similar to what occurs in RcPointer. What we do with memory once it has been allocated is very similar to what is done in counted pointers. Our array contains a pointer to an object of type TypeRep. This abreviated version of typerep shows the functions and data members associated with the class.

template < class Type>
class TypeRep{
friend class Array< Type>;
private:
	TypeRep(){_active = 0;}
	TypeRep(int newSize, int delta);
	~TypeRep() ;
	TypeRep* copy();
	Type *rep;
	int _size;
	int _active;
	int sizeChange;
	int count;
};
Notice that all of the functions and data members are private. It would seem that it is impossible to do anything with this class since even the constructors are private. Note that the class is declared as a friend of Array. This combination makes the class TypeRep a captive of the class Array. The same thing can be achieved by nesting the definition of TypeRep within the definition of Array. This was done with the type Stor in RcPointer, but was not done here since TypeRep has some functions that cannot be declared inline (within the class definition), and most current compilers have difficulty with this type of function definition.

TypeRep also has a destructor ~TypeRep. Any class that allocates memory should have a destructor to release the memory when a variable of this type is no longer in scope (accessible). Destructors are the cause of many problems for c++ programmers since they are automatically called by c++ when a variable leaves scope. This can be frustrating when a variable is passed by value to a function. When the function is through, the parameter is no longer in scope and the memory allocated to the variable is released. If a proper copy of the original variable has not been made, the memory released is in the original variable. This leaves the program without data.

We will avoid these problems by having our class count the number of variables that share the memory location. This count is kept in the variable count. Every time a copy of the variable is made the count is incremented, and when a variable is no longer in scope the count is decremented. Since the variables that are visible are members of the array class, that class will have the responsiblity of maintaining the count.

The only other function in this class is copy. It is needed to create a new array with the same data as the original. All copies created in the normal way are just aliases for the same memory location, so if you want to change one copy and have the others remain unchanged, you must have a copy function that creates another array. The other data members are used for housekeeping and will be described later.

The Class Array

Memory Management Functions

The memory management functions for the array class its constructors and destructor. The first two constructors are used to create a new array in an initial state and are fairly standard. The copy constructor (the third constructor) is called by the system when an array is created from an existing array. In this case the pointer to the TypeRep of the existing array is copied to the data pointer in the new array. The TypeRep's counter is also incremented to indicate that another variable can access this data.

The destructor of the array, must first tell the TypeRep that it has one less viewer. This is done by predecrementing the counter. Once the counter has been decremented, the destructor checks to see if there are any other variables sharing this data. If not, the count is zero and the TypeRep is deleted. When this pointer is deleted, the destructor for TypeRep is called and it releases the memory holding the actual array. If the count is not zero, the constructor does not delete the TypeRep.

// Constructors

template <class Type> Array::Array(int newSize, int delta) { data = new TypeRep(newSize,delta); if(data == NULL) throw "Insufficient memory to create a new array"; } template <class Type> Array::Array(int newSize, int delta, Type val) { data = new TypeRep (newSize,delta); if(data == NULL) throw "Insufficient memory to create a new array"; for(int i = 0;i_size;i++) data->rep[i] = val; } template <class Type> Array::Array(const Array &x) { data = x.data; data->count++; }

// Destructor

template <class Type> Array::~Array() { if (--data->count ==0) delete data; }

//Assignment Operator

template <class Type> Array& Array::operator = (const Array &s) { s.data->count++; if (--data->count ==0) delete data; data = s.data; return *this; }