![]() |
Essentials of C++: Section XV |
Templates
A. Function Templates A function template is a general description that will be used to create specific instances of the function - where the specific instances are called template functions. Each general description begins with a template header with the form:
Examples: The first example says that the function definition to follow will use
one type parameter while the second one will use two type
parameters. It is these parameters that determine what types a specific
function formed from this general type will use. To define the function template one then adds what looks like a
standard function definition except that it uses the type parameters found
in the template header. For example, to declare a function template that
compares two elements of the same type and return the larger:
template <class Typ)
Typ Compare(Typ elem1, Typ elem2)
{
if (elem1 > elem2)
{
return elem1;
}
else
{
return elem2;
}
}When this function is called, the types of the parameters used in
the function call determine the types in the specific template function
created. Since the types of parameters are known at compile time, it is
the compiler that creates the different instances of the general function
description. If 'Compare' is called one or more times with integer
parameters, one or more times with double parameters, and one or more
times with parameters of some class type, the compiler will create three
instances of the function 'Compare'. Which version is called each time is
then handled using the same approach used for function overloading. In
fact, one can see this as a form of compiler generated function
overloading. The programmer could accomplish the same goal using three explicitly defined overloaded functions but then the burden of work falls on the programmer. Function templates allow the programmer to declare a single, general definition of a function as long as all types using this general definition require the same code. One can mix overloading and function templates in cases where some types require special code. The compiler will first try to find specific function matches and only then will it work with the function template. All the type parameters in the template header must be included in the
function definition. In addition, if a class type is passed as an actual
parameter, any operators used in the function definition must be defined
for the class type. For example, in the above code, if 'Compare' is called
with two elements of type 'Class1' then the '>' operator better be
defined for 'Class1'. It is perhaps unfortunate that the word 'class' is used in the function
header. It has nothing to do with the object oriented programming notion
of class and simply states that the parameter name that follows represents
a type - any type, built-in or user defined. B. Class Templates Consider a queue, a class representing an arrangement of elements where elements are deleted from one end and added at the other - as in a line or queue at a theatre. In general, one can have a queue of persons, animals, processes (in an operating system) etc. To declare a template for this class, one starts with the same header form as with function templates:
followed by the class declaration, as in:
template <class Typ>
class Queue
{public:
Queue(int sz);
~Queue ();
Add(Typ elem);
Typ Delete();
// other member functions
private:
int sizeOfQueue;
int front;
int rear;
Typ* queue;
};
In this class declaration the type parameter, 'Typ' is used to
indicate:
To declare a specific type of queue class to contain doubles, one could
write: Upon seeing this, the compiler creates a class we might call
'QueueOfDoubles' and instantiates the instance 'queueForDoubles' to hold
up to 50 doubles. To define the member functions of a class template, one uses the class
template's header followed by a slightly modified version of the standard
code for member functions. For example, here is the code for the three
member functions for Queue:
template <class Typ>
Queue<Typ> :: Queue (int sz)
{ sizeOfQueue = sz;
queue = new Typ [size];
front = 0;
rear = 0;
}
template <class Typ>
Queue<Typ> :: ~Queue ()
{
delete [] queue;
}
template <class Typ>
Queue<Typ> :: Add(Typ elem)
{ if (rear < size) // simplified to ignore issues of circular
// queues etc.
{ queue[rear] = elem;
rear++;
}
else
{
cout << "\nNo room to add\n";
// better if queue returned success or failure
}
}
template <class Typ>
Typ Queue<Typ> :: Delete()
{ if (front < rear) // simplified to ignore issues of
// circular queues etc.
{ return queue[front--];
}
else
{ cout << "\nQueue is already empty\n";
// better if queue returned success or failure
}
}
This code does not handle the intricacies of queues but it does
demonstrate how to define class template member functions. Note that for
each member function the template header is repeated and then the class
name followed by angle brackets,<....>, with the type parameter(s)
inside the brackets. When different template classes are created from a
single general form, the information in the brackets will allow the
compiler to keep the instances and their associated member functions
separate from each other. It is also legal to include non-type parameters as arguments for the
template header. For example, if one wanted to allow the user of the class
template to declare classes of queues with specific sizes,one could
write:
template <class Typ, int size>
class Queue
{public:
Queue();
~Queue ();
Add(Typ elem);
Typ Delete();
// other member functions
private:
int sizeOfQueue;
int front;
int rear;
Typ* queue;
};
The constructor would no longer receive the size and declarations of
specific queues would take the form used in the following example:
Upon seeing this, the compiler creates a class we might call
'QueueOf50Doubles' that will hold 50 doubles and instantiates the instance
'queueForDoubles'. The difference between this and the early declaration
is that this time it is the class QueueOf50Doubles' that determines the
size of the queue, not the instantiation of the specific instance. There are a number of issues related to friends and class templates as well as static members of class templates. You should consult other books and your manual for more information. C++ How to Program by H.M. Deitel and P.J. Deitel provides a brief but good introduction to these two topics. C. Class Templates and File Linkage Herein lies a problem with the normal way we compile and link code. When we compile the .cpp file containing the definitions for the 'Queue' class, there is normally no information given to the compiler about how these definitions will be used. In our example, then, the compiler is not told that there will be one or more queues of integers and one or more queues of doubles. Thus, the compiler has no idea that it should define member functions for a queue of doubles and a separate set of member functions for a queue of integers. However, when the compiler works on a program file with instance declarations for queues of integers and queues of doubles, it does assume the existence of the appropriate member functions. The compiler makes this assumption, of course, because it has access (via an #include statement) to the header file containing the declarations for class 'Queue'. It sees the declaration and fills in the appropriate values for the type parameters. In other words, it interprets the header file as a 'promise' that the appropriate function definitions will be provided later. It is at link time that the problem occurs. No such functions are found and one receives the type of error message one gets when forgetting to define a function or misspelling its name in the definition file. There are two solutions to this problem. One is to ignore the principles behind separate compilation of files and include the code for the member functions in the header file for the class declaration. Now, when the declaration file is included in the program file, the member function definitions end up in the same file as the instance declarations. Since the compiler sees both the instance declarations and the generic member functions at the same time, it knows what type of specific member functions to create. In our example, if the program file ends up with the generic member function definitions and instance declarations for "queues of doubles" and for "queues on integers", it will create a set of queue member functions to handle doubles and a set of queue member functions to handle integers. The second solution involves the use of special compiler 'switches'. What follows is the approach used in Borland C++. In the program file, include, towards the top, the line: This tells the compiler to expect the template instance to be defined elsewhere. This is an extension of the use of the keyword 'extern'. Then, in the file containing the class definitions, include the line: Follow this line with code that lists all the specific types of instances that need to be created. If one wants queues of integers and queues of doubles, the code would be:
The 'Jgd' tells the compiler to generate public definitions accessible from other files for each of the instance types listed. In other words, the scope of the member functions of both class instances includes any file with the –Jgx switch. The two 'typedef' lines inform the compiler that it is to create member functions for both a "queue of doubles" class and a "queue of integers" class. The identifiers 'version1' and 'version2' are simply placeholders. They are necessary for the syntax of 'typedef' but do nothing and are not used. Templates can simplify the work of a programmer but only at a price. Much has been skipped. Specifically, there are a number of issues related to friends and class templates as well as static members of class templates. You should consult other books and your manual for more information. C++ How to Program by H.M. Deitel and P.J. Deitel provides a brief but good introduction to these two topics.
|