Templates
recall back to
the sorting routines we wrote way back. There are many times that a sort
could be used for several different types of data. This is where generic
programming comes into play.
A template is
related to a macro in that in a C macro, you can use #define to replace a given
symbol with a set value throughout a program.
Example of C
macro:
#define sqr(x) x*x
this is a
function that will return the square of an input. To use it you
would code the following:
int
a = 7;
cout << sqr(a);
when this is
compiled, the compiler will replace the (a) with
(a*a)
what if you
coded:??
int c,d;
cout <<
sqr(c+d);
You would think
this would work but in fact it will not give you the result you may think.
Look closely… when it is compiled, the compiler replaces what is
within the parentheses with what is specified in the macro. In this case
it would be (c+d*c+d). Due to
the hierarchy of operands this would be equivalent to saying (c+(d*c)+d) instead of ((c+d)*(c+d))
This leads us to
the use of templates. Lets recall a standard swap
function:
Standard
function:
void swap(string &a, string &b)
{
string
temp;
temp = a;
a = b;
b = temp;
}
Now to convert
this to a template, the only thing we need to change are the highlighted
variables; A template is basically a macro on steroids. It will even
let you take entire functions and classes and treat them as
macros.
Template:
template <class T>
// must ALWAYS have this
line for a template
void swap(T&a, T&b)
{
T temp;
temp = a;
a =
b:
b =
temp;
}
Note that
whenever you see 'T' , it represents a type of some sort. Whenever it is
called it will insert the type of the call and generate code for a swap of that
type. This won't compile at this stage. You first need to create an
instance for it's use because the compiler doesn't know what the type T
is.
Templates will
go in your .h file (or above the main() in a small coding where you would
generally put your class definitions. Then in the main() you can
code:
main()
{
int
a,b;
double c,d;
string e,f;
swap(a,b);
swap(c,d);
swap(e,f);
}
When this
compiles, the compiler will see the first swap function call and determine that
a swap function is needed for integers. Since it doesn't find one
but does find the template, it will then generate the code for an integer swap
function. Likewise, on the following two lines it will do the same for a
double swap and a string swap. It will only generate code for
templates when it is needed.
There is another
nice aspect of using templates. Say you wanted to swap (e,b) which is a
string and an integer. It won't let you do this because your template
function header states that is is taking a T &a, and a T &b. So
the two types have to be the same. Trying to do this would give you a
compile time error.
But.. You can
write a template to take more than one type.
Example:
template <class T, class S>
func(T &a, S &b)
{
whatever code …
}
Note:
your symbols for the parameters can be any legal
variable but T is by far the most common one used.
Let's go one
step further and make an entire class into templates (List class) so we can use
it for integers, doubles, strings, or CD's.
Standard List
class
struct Node
{
int
data;
Node *next;
};
class List
{
Node *head;
public:
List();
//
constructor
List(const List &);
// copy constructor
List & operator =(const List
&);
~List();
...
};
Template List
class
template <class T>
struct Node
{
T data;
Node <T> *next;
};
template <class T>
class List
{
Node <T> *head
public:
List <T>();
List <T>(const List <T> &);
List <T> & operator =(const List <T> &);
~List <T>();
int
insert(const T&);
};
Now we have a
template class definition which we can use for any type of data but we need to
implement the functions...
_____________________________________________________________
template <class T>
//
constructor
List <T>::List <T>()
{
whatever you need to initialize
head = 0;
}
______________________________________________________________
template <class T>
List <T>::List <T>(const List <T> &)
// copy constructor
{
...
}
_______________________________________________________________
template <class T>
int
List <T>::insert(const T & value)
{
...
}
Notice that in the insert
function you are just passing in a const T instead of List <T> This
may seem confusing at
first but remember that you
are passing a data type such as a double or a string but you are using it in a
template
so it is of class type T.
This is different than in the copy constructor where you are passing in a
reference to a List.
In that case you need to
say what type of List you are passing. Hence the parameterized
List<T>.
Important
Note: This won't compile in the C file like
normal. You must put all code for templates in the .h file including class
definition, implementations, …
Now in your
Test.C file…
#include "List.h"
...
...
main()
{
List <int> list1;
// when you get here, the compiler will break
loose
// and start to create a list
class for integers but it will
// only generate code for parts of
the List class that
// are needed in this
program.
List <string> list2;
List <CD> list3;
list1.insert(3);
// generates insert function for int
now
list2.insert("Hello");
list3.insert(my_cd);
In each case,
the compiler will generate a new function for each type of List but since it's a
template, the compiler will do it for you automatically. Now you can see
how you can take advantage of the fact that you don't need to change any
code.
How does this
effect your Makefile?
You can't
compile a template unless you have a particular type to
compile.
test:test.o
g++ -Wall -o test test.o
test.o:test.C List.h
g++ -Wall -c
test.C
|