|

Templates allow you to build type-safe collections.
Templates are sometimes considered superior to linked lists
because linked lists can only handle the particular
data objects they were created to work with. You couldn't place a
different object into a linked list without modifying it.
Templates allow you to
teach the compiler how to make a list
of any type of thing, rather than creating a set of type-specific lists. The type
of the thing in the list becomes a parameter to the definition of the class.
The Template class concepts we will deal with are
the same as the concepts that cover our standard classes.
So therefore,
template instantiation
is the act of creating a specific type from a template, and
template instances
are marked by the individual class data types using the template. Templates allow
you to create a GENERAL CLASS and pass "types" as parameters to
that class to build specific instances. Therefore we can
say that a Template is a
class that is a parameterized List object. Are we
thoroughly confused now? Fear not, brave one, this too
shall pass. :) You declare a
template by writing:
"Physics
is the universe's operating system."
- Steven R Garman
"Only
two things are infinite, the universe and human
stupidity, and I'm not sure about the former."
- Albert Einstein
|
template <class T>
class List
{
public:
List();
//full class declaration here
}; |
The keyword "template" must be used at
the beginning of every declaration AND definition of a template class. The parameters of the template are written after it - they are the items that will change with each instance. In the expression "template <class T>",
the keyword "class" indicates that this is a parameter type and
that the identifier, "T", is used throughout the rest of the template definition to refer to the parameterized type
(the class). One instance of this class will substitute "int" wherever "T" appears, another instance will substitute "Monster". To declare an "int"
and "Monster"
instance, we would write:
List<int> IntList;
//would
create a List of int objects List<Monster>
MonsterList;
//would
create a List of Monster objects
The trick to template
design is to get your template working on a single object/class type, then parameterize
it, generalizing the template to handle any type of object. Example:
// File 1 of 2. This file is a header file called "Template.h"
// Every class used with the template MUST have a constructor that takes an int,
// an "itsSize" data member, and a "GetSize()" accessor.
#include <iostream.h>
class Monster {
public:
Monster() {}
Monster(int sz) { cout << "Monster created.\n"; }
~Monster() { cout << "Monster destroyed.\n"; }
int GetSize() { return itsSize; }
void SetSize(int str) { itsSize = str; }
private:
int itsSize;
};
/*--------------------------------------------------------------------------------------------*/
template <class T> // Declare the template and the generic parameter, "T". T represents
// whatever object we will pass in to the template. Functions like a typedef
// for multiple classes. This must come directly BEFORE the template class below .
/*--------------------------------------------------------------------------------------------*/
class GenericArrayOfObjectsTemplate //The class being parameterized
{
public:
//Constructor For some "odd" reason, Monsters come in sets of 3
//Remember what the songs says - "3 is a magic number"... :)
GenericArrayOfObjectsTemplate(int size = 3) {
itsSize = size;
//Below we are saying that pType will become a pointer of whatever type of object
//we pass in to the template and will point to an array of whatever type
//of object we pass in to the template. T is declared a pointer in the private member data.
pType = new T[size];
for (int i = 0; i < size; i++) {
pType[i] = 0; }
} //close constructor
//Copy constructor
GenericArrayOfObjectsTemplate(const GenericArrayOfObjectsTemplate & rhs) {
itsSize = rhs.GetSize();
pType = new T[itsSize];
for(int i = 0; i < itsSize; i++) {
pType[i] = rhs[i]; }
} //close copy constructor
//Destructor
~GenericArrayOfObjectsTemplate() { delete [] pType; }
// Overloaded Operator =
GenericArrayOfObjectsTemplate& operator=(const GenericArrayOfObjectsTemplate &) {
//Just make sure it's not the object itself trying to copy itself
if (this == & rhs)
return *this;
delete [] pType;
itsSize = rhs.GetSize();
pType = new T[itsSize];
for (int i = 0; i < itsSize; i++)
pType[i] = rhs[i];
return *this;
} //close function
//2 Overloaded Operator [], one constant and one not
T & operator[](int offSet)
{ return pType[offSet]; }
const T & operator[](int offSet) const
{ return pType[offSet]; }
//Accessors
int GetSize() const { return itsSize; }
//friend function prototype/declaration. Remember that a friend
//function must be defined outside the class specification.
friend void Intrude(GenericArrayOfObjectsTemplate<int>);
private:
//T represents what this will be a class of - the template
T *pType;
int itsSize;
};
/*--------------------------------------------------------------------------------------------*/
// Friend Function - Intrude Declared type <int>, can only be used
// with int arrays! No OTHER data type. Intrudes into private data.
void Intrude(GenericArrayOfObjectsTemplate<int> theGenericArrayOfObjectsTemplate) {
cout << "\n*** Intrude <int> Template***\n";
for(int i = 0; i < theGenericArrayOfObjectsTemplate.itsSize; i++) {
cout << "i: " << theGenericArrayOfObjectsTemplate.pType[i] << endl; }
cout << "\n";
}
//File 2 of
2. This file is the source file for main().
#include
"Template.h"
void main() {
GenericArrayOfObjectsTemplate<int>
Test1;
GenericArrayOfObjectsTemplate<float>
Test2;
GenericArrayOfObjectsTemplate<long>
Test3;
GenericArrayOfObjectsTemplate<double>
Test4;
GenericArrayOfObjectsTemplate<char>
Test5;
GenericArrayOfObjectsTemplate<Monster> Test6;
//Can only be used with "int"
instances of the template
Intrude(Test1);
} |
Output:
Monster created.
Monster destroyed.
Monster created.
Monster destroyed.
Monster created.
Monster destroyed. |
*** Intrude <int> Template***
i: 0
i: 0
i: 0
Monster destroyed.
Monster destroyed.
Monster destroyed. |
In the example above, we
set up a template class to allow us to create an array of
whatever type of objects we pass into it. In main(), six
different objects and class instances are created. The
first five objects are instantiated from built-in types.
The last object is created from our own user-defined class,
Monster. In effect, it is as if we had created six
different classes, though we did it with one declared template
class, the
GenericArrayOfObjectsTemplate.
Also notice that
template
<class T>
has to come just before the
template class itself. Position and placement of the
template declaration keyword matters when declaring and defining
template classes. We made the function,
Intrude(), a
friend function so that it could be accessed by objects
that were not of the class itself. Remember that a friend
function, in addition to using the keyword "friend", must be
defined outside the class. This enables them to
circumnavigate restrictions on data members that would otherwise
be private or inaccessible to objects that were not of that
class or that did not derive from it. In addition to class
templates, we can create function templates:
template <typename T>
void swap(T
& x, T
& y)
{
T tmp = x;
x = y;
y = tmp;
}
int main()
{
int i,j;
swap(i,j);
//Instantiates a swap
for int
float a,b; swap(a,b);
//Instantiates
a swap for float
char c,d;
swap(c,d);
//Instantiates a swap
for char
} |
Let's
combine a template class and a linked list to handle three
different kinds of objects:
#include <iostream.h>
enum { ValueIsSmaller,
ValueIsLarger, ValueIsSame};
//--------------------------------------------------------------------------------------------------------------------
// First object class to put into the linked list. Any class in this linked list must support two methods:
// Show (displays the value) and Compare (returns relative position)
class Data
{
public:
Data(int val):myValue(val){}
~Data()
{
cout << "Deleting Data object with value: ";
cout << myValue << ".\n";
}
int Compare(const Data
&);
void Show()
{ cout << "The value of this Data object is " <<
myValue << ".\n"; }
private:
int myValue;
};
//Compare is used to decide where in the list a particular object belongs.
int Data::Compare(const Data
& theOtherObject)
{
if(myValue < theOtherObject.myValue)
return ValueIsSmaller;
if(myValue > theOtherObject.myValue)
return
ValueIsLarger;
else
return
ValueIsSame;
}
//--------------------------------------------------------------------------------------------------------------------
//
Another class to put into the linked list. Again,
every class in this linked list
// must support two methods: Show (displays the
value) and Compare (returns relative position).
class Monster
{
public:
Monster(int
strength): mystrength(strength){}
~Monster()
{
cout << "Deleting the Monster
that has strength ";
cout << (mystrength * 100) <<
".\n";
}
int Compare(const
Monster &);
void Show()
{ cout << "This
Monster has strength " << (mystrength * 100) <<
"!\n"; }
private:
int mystrength;
};
//Compare is used to decide where in the list a
particular object belongs.
int Monster::Compare(const
Monster &
theOtherMonster)
{
if(mystrength <
theOtherMonster.mystrength)
return
ValueIsSmaller;
if(mystrength > theOtherMonster.mystrength)
return
ValueIsLarger;
else
return ValueIsSame;
}
//--------------------------------------------------------------------------------------------------------------------
// Another class to put into the linked list. Again, every class in this
linked list
// must support two methods: Show (displays the value) and Compare (returns relative position).
class Cat
{
public:
Cat(int age): myAge(age){}
~Cat()
{
cout << "Deleting
the Cat that is ";
cout << myAge << " year(s)
old.\n";
}
int Compare(const Cat
&);
void Show()
{ cout << "This cat is " << myAge << "
year(s) old!\n"; }
private:
int myAge;
};
//Compare is used to decide where in the list a particular object belongs.
int Cat::Compare(const Cat
& theOtherCat)
{
if(myAge < theOtherCat.myAge)
return
ValueIsSmaller;
if(myAge > theOtherCat.myAge)
return
ValueIsLarger;
else
return ValueIsSame;
}
//--------------------------------------------------------------------------------------------------------------------
// ADT representing the node object in the list.
As an ADT, it will never
// have a direct instance. Every derived class must override Insert and Show.
template <class
T>
class Node
{
public:
Node(){}
virtual ~Node(){}
virtual Node
* Insert(T
* theObject) =
0;
virtual void Show() =
0;
private:
};
//--------------------------------------------------------------------------------------------------------------------
template <class
T>
class InternalNode:
public Node<T>
{
public:
InternalNode(T
* theObject, Node<T>
* next);
~InternalNode(){ delete myNext;
delete myObject; }
virtual Node<T> * Insert(T * theObject);
virtual void Show()
{
myObject->Show();
myNext->Show();
}
//delegate!
private:
T
* myObject;
//the Object itself
Node<T> * myNext;
//points to next node in the linked list
};
//Constructor
definitions - all the constructor does is initialize.
template <class
T>
InternalNode<T>::InternalNode(T
* theObject, Node<T>
* next):
myObject(theObject),myNext(next)
{
}
// When you put a new object into the list it is passed to the node which figures out
// where it goes and inserts it into the list.
template <class
T>
Node<T> * InternalNode<T>::Insert(T
* theObject)
{
//Is the new value bigger or smaller than me?
int result = myObject->Compare(*theObject);
switch(result)
{
// by convention if it is the same it comes first
case ValueIsSame:
//fall through
case ValueIsLarger:
//new Object comes before me
{
InternalNode<T>
* ObjectNode = new InternalNode<T>(theObject, this);
return ObjectNode;
}
case ValueIsSmaller:
//It is bigger, so pass it on to the next node and let it handle it.
myNext = myNext->Insert(theObject);
return this;
}
//close switch
return
this;
}
//--------------------------------------------------------------------------------------------------------------------
//Tail node is just a sentinel
template <class
T>
class TailNode :
public Node<T>
{
public:
TailNode(){}
~TailNode(){}
virtual Node<T>
* Insert(T
* theObject);
virtual
void Show() { }
private:
};
//If Object comes to me, it must be inserted before me as I am the tail and NOTHING comes after me.
template <class
T>
Node<T> * TailNode<T>::Insert(T
* theObject)
{
InternalNode<T>
* ObjectNode = new InternalNode<T>(theObject, this);
return ObjectNode;
}
//--------------------------------------------------------------------------------------------------------------------
//The Head node has no Object, it just points to the very beginning of the list.
template <class
T>
class HeadNode :
public Node<T>
{
public:
HeadNode();
~HeadNode() { delete myNext; }
virtual Node<T>
* Insert(T
* theObject);
virtual void Show() { myNext->Show(); }
private:
Node<T> * myNext;
};
//As soon as the head is created, it creates the tail.
template <class
T>
HeadNode<T>::HeadNode()
{
myNext = new TailNode<T>;
}
//Nothing comes before the head, so just pass the
object on to the next node.
template <class
T>
Node<T> * HeadNode<T>::Insert(T
* theObject)
{
myNext = myNext->Insert(theObject);
return this;
}
//--------------------------------------------------------------------------------------------------------------------
//This
class starts the ball rolling and the other classes
implement the List.
template <class
T>
class LinkedList
{
public:
LinkedList();
~LinkedList() { delete myHead; }
void Insert(T
* theObject);
void ShowAll() { myHead->Show(); }
private:
HeadNode<T>
* myHead;
};
// At birth,
LinkedList creates the head node. The head
node then creates the tail node by its
// constructor. So an empty list points to the head which points to the tail and has nothing between.
template <class
T>
LinkedList<T>::LinkedList()
{
myHead = new HeadNode<T>;
}
//Delegate the task out to other classes
template <class
T>
void LinkedList<T>::Insert(T
* pObject)
{
myHead->Insert(pObject);
}
//--------------------------------------------------------------------------------------------------------------------
int main()
{
Cat * pCat;
Data * pData;
int val;
LinkedList<Cat> ListOfCats;
LinkedList<Data> ListOfData;
//Ask the user to produce some values
and put them in the list.
for(;;)
{
cout << "What value? (0 to stop): ";
cin >> val;
if(!val)
break;
pMonster =
new Monster(val);
pData = new Data(val);
pCat = new Cat(val);
ListOfMonsters.Insert(pMonster);
ListOfData.Insert(pData);
ListOfCats.Insert(pCat);
} //close for
loop
//Let ShowAll() iterate through the list and show the object.
cout << "\n ************ \n";
ListOfMonsters.ShowAll();
cout << "\n";
ListOfData.ShowAll();
cout << "\n";
ListOfCats.ShowAll();
cout << "\n ************ \n\n";
return 0;
//On
exit, the lists fall out of scope and are destroyed!
} |
Output:
What value? (0 to stop): 1
What value? (0 to stop): 2
What value? (0 to stop): 0
************
This Monster has strength 100!
This Monster has strength 200!
The value of this Data object is 1.
The value of this Data object is 2. |
This cat is 1 year(s) old!
This cat is 2 year(s) old!
************
Deleting the Cat that is 2 year(s) old.
Deleting the Cat that is 1 year(s) old.
Deleting Data object with value: 2.
Deleting Data object with value: 1.
Deleting the Monster that has strength 200.
Deleting the Monster that has strength 100. |
In the template in linked
list combination above, the same list can handle three different
objects since the Node (ADT), Internal Node, TailNode and
HeadNode have been declared as template classes. Note that
each class declaration and method is preceded with "template class <T>". This tells the compiler that you are parameterizing this list on a type that you will define later when you instantiate the list. So the declaration of the Node class must now become:
template <class T> class Node
This says that Node will not exist as a class in and of itself, but rather that you will instantiate
Nodes of Monsters, Nodes of Cats and Nodes of Data objects.
The actual type you will pass in is represented by "T".
The InternalNode will now become "InternalNode<T>". The InternalNode<T> points to a "T" (whatever type of object
is passed to it) and a Node<T>, not to a hard-coded Data object and another Node.
The Insert() has been re-defined. Where there used to be a specific type
of object, Data, now there is "T", representing whatever object
is passed in. The parameter is a pointer to "T". Later, when specific lists are instantiated,
the compiler will replace "T" with the corresponding instances of
Monster, Data and Cat. In this way, the InternalNode can continue working indifferent to the actual
data type used. It only asks the objects to compare themselves.
For this reason, each object passed into the template must
support two methods: Show() and Compare(). Depending
on the nature of the object's data members, these methods will
differ from class to class. But as long as they are
provided, this List can sort any number of generic objects.
You can treat template items just as any other type. You can pass them as parameters by reference/pointer or
by value, and you can return them as return values to functions, also by value or
by reference. Below is a rewrite of main() using the above
example, to demonstrate passing template objects by reference
and using them in functions. You will need to include all
of the code in the table above up to the main() function.
//Include all of the class declaration code listed
in the table above for Monster, Cat, and Data
//objects as well as the ADT Node, and the classes
InternalNode, HeadNode, TailNode, and LinkedList.
//Then, just after the LinkesList class, add the
code for the two functions and main() below.
//--------------------------------------------------------------------------------------------------------------------
//Function prototypes
void MonsterFunction(LinkedList<Monster>
& ListOfMonsters);
void CatFunction(LinkedList<Cat> &
ListOfCats);
void DataFunction(LinkedList<Data>
& ListOfData);
//--------------------------------------------------------------------------------------------------------------------
int main()
{
//Create instances of the template classes, passing
in Monster, Cat and Data
LinkedList<Monster> ListOfMonsters;
LinkedList<Cat> ListOfCats;
LinkedList<Data> ListOfData;
//Call the functions
MonsterFunction(ListOfMonsters);
CatFunction(ListOfCats);
DataFunction(ListOfData);
//Iterate through
the list and show objects
ListOfMonsters.ShowAll();
cout << "\n";
ListOfCats.ShowAll();
cout << "\n";
ListOfData.ShowAll();
cout << "\n ************ \n\n";
return 0; // The lists fall out of scope and are destroyed!
}
//--------------------------------------------------------------------------------------------------------------------
//Function definitions
void MonsterFunction(LinkedList<Monster>
& ListOfMonsters)
{
Monster * pMonster;
int val;
//Ask the user to produce some values, put them in the list.
for (;;)
{
cout << "\nOf what strength is your Monster? (0 to stop): ";
cin >> val;
if(!val)
break;
pMonster = new Monster(val);
ListOfMonsters.Insert(pMonster);
} //close
infinite loop
} //close
function
void CatFunction(LinkedList<Cat>
& ListOfCats)
{
Cat * pCat;
int val;
//Ask the user to produce some values, put them in the list.
for (;;)
{
cout << "\nHow old is your cat? (0 to stop): ";
cin >> val;
if(!val)
break;
pCat = new Cat(val);
ListOfCats.Insert(pCat);
} //close
infinite loop
} //close
function
void DataFunction(LinkedList<Data>
& ListOfData)
{
Data * pData;
int val;
//Ask the user to produce some values and put them in the list.
for(;;)
{
cout << "\nWhat value? (0 to stop): ";
cin >> val;
if(!val)
break;
pData = new Data(val);
ListOfData.Insert(pData);
} //close
infinite loop
} //close
function |
Output:
Of what strength is your
Monster? (0 to stop): 1
Of what strength is your Monster? (0 to stop): 2
Of what strength is your Monster? (0 to stop): 0
How old is your cat? (0 to stop): 1
How old is your cat? (0 to stop): 2
How old is your cat? (0 to stop): 0
What value? (0 to stop): 1
What value? (0 to stop): 2
What value? (0 to stop): 0 |
This Monster has strength 100!
This Monster has strength 200!
This cat is 1 year(s) old!
This cat is 2 year(s) old!
The value of this Data object is 1.
The value of this Data object is 2.
************
Deleting Data object with value: 2.
Deleting Data object with value: 1.
Deleting the Cat that is 2 year(s) old.
Deleting the Cat that is 1 year(s) old.
Deleting the Monster that has strength 200.
Deleting the Monster that has strength 100. |
Like previous code, but this time LinkedLists are passed by reference to their respective functions for processing.
This can be powerful. Once lists are instantiated, they can be treated as fully defined types, passed into functions and returned as values.
Each instance of the template is an actual object, and can be
used like any other object, as though it were a separate class
instance.
STL - The
Standard Template Library is offered with most
compilers as part of their headers and libraries. It is a
library of template-based container classes, including vectors, lists, queues and stacks.
These group together some common algorithms for sorting and searching.
The essential goal of the STL is to give you an alternative to reinventing the wheel.
You can stand on the shoulders of the programming giants who
have come before you. You can plug STLs into all your programs for specific functions for easier and faster development than if you
had to code everything from scratch.
Yes - it is now time to pay homage to our venerable,
unappreciated, overworked coding ancestors.
:) We must send them a
"thank you" out across the ether every now and then, as we
grimace at their horror stories of misplaced punch cards,
Fortran and iron core memory. To all coding veterans who
sailed the murky seas and brackish waters of the dark ages
before cheap computers and GUIs to spare, - we salute you!
Now, back to the STL. Some of the most useful STL classes and algorithms are listed
below with links:
©2004 C. Germany
|