Class Templates
class Stack1
{
public:
Stack1(int capacity) : items_(new int[capacity]), count_(0)
{
}
~Stack1()
{
delete[] items_;
}
void Push(int item)
{
items_[count_++] = item;
}
int Pop()
{
return items_[--count_];
}
bool IsEmpty()
{
return (count_ == 0);
}
private:
int *items_; // The array of integers
int count_; // The number of integers on the stack
};
Using the first Stack class:
|
Output:4 3 2 1 0 |
|
|
A Stack Implementation using a Template
| Class Definition | Implementations |
|---|---|
|
|
Self-check: What are the requirements for T in the templated Stack class above?
Pay close attention to the syntax for implementing the templated member functions.
Template syntax:
template <typename T> Stack<T>::
Implementing the methods within the class:
template <typename T>
class Stack2
{
public:
Stack2(int capacity)
{
items_ = new T[capacity];
count_ = 0;
}
~Stack2()
{
delete[] items_;
}
void Push(const T& item)
{
items_[count_++] = item;
}
T Pop()
{
return items_[--count_];
}
bool IsEmpty()
{
return (count_ == 0);
}
private:
T *items_;
int count_;
};
Note: When you put the method definitions inside the class (as shown above), you do not need to duplicate the template <typename T> preface that is required outside of the class. I'm only showing this style here to keep the notes from becoming too verbose.
Using this Stack class is almost identical to using the first one:
|
Output:4 3 2 1 0 |
We can now create a Stack of doubles:
|
Output:0.4 0.3 0.2 0.1 0 |
A Stack of char *:
|
Output:
Three Two One |
We can even create a Stack of StopWatch objects:
StopWatch.cpp
|
Output:
00:02:00 00:01:30 00:01:00 |
The compiler generates code similar to this code for the above. This automatic code generation is called template instantiation.
Notes:
Stack2<int> s1(10); // Code for int Stack is generated. Stack2<int> s2(10); // Nothing is generated. int Stack already exists. Stack2<int> s3(20); // Nothing is generated. int Stack already exists. Stack2<double> s4(10); // Code for double Stack is generated.
class Foo; // forward declaration, no definition needed Foo *fp; // Ok, pointer, no definition needed Foo f; // Error, Foo undefined
Stack s; // What will be instantiated? (The Stack class is a templated class)
Non-Type Template Arguments
Suppose we want to create a class that represents an array of integers. We want the size to vary depending on how it's instantiated:class IntArray1
{
public:
IntArray1(unsigned int size) : size_(size), array_(new int[size])
{
}
~IntArray1()
{
delete [] array_;
}
// ...
private:
int size_;
int *array_; // dynamically allocated array (at run-time)
};
We can use the class like this:
Using a non-type template parameter:IntArray1 ar1(20); // Dynamic allocation at runtime (20 ints) IntArray1 ar2(30); // Dynamic allocation at runtime (30 ints)
template <unsigned int size>
class IntArray2
{
public:
IntArray2() : size_(size)
{
}
// ... (no destructor needed)
private:
int size_;
int array[size]; // static array (allocated at compile-time)
};
Using this template class:
Notes:IntArray2<20> ar3; // Static allocation at compile time (20 ints) IntArray2<30> ar4; // Static allocation at compile time (30 ints)
const unsigned int csize = 30; unsigned int size = 20; IntArray2<10> ar5; // Ok, literal constant expression IntArray2<csize> ar6; // Ok, constant expression IntArray2<csize + 5> ar7; // Ok, constant expression IntArray2<size> ar8; // Error, non-const expresson // All refer to the same instantiation IntArray2<csize> arA; // IntArray2<30> IntArray2<5 * 6> arB; // IntArray2<30> IntArray2<32 - 2> arC; // IntArray2<30>
Multiple and Default Template Arguments
You can specify more than one template argument.In this modified Array class, we specify not only the type of the elements in the array, but the size as well:
template <typename T, unsigned int size>
class Array
{
public:
Array() : size_(size)
{
}
// ... (no destructor needed)
private:
int size_;
T array[size]; // static array (allocated at compile-time)
};
Usage:
Array<int, 10> ar1; // int array[10] Array<double, 20> ar2; // double array[20]; Array<StopWatch, 30> ar3; // StopWatch array[30]; Array<StopWatch *, 40> ar4; // StopWatch *array[40]
Self-check: What are the requirements for T in the templated Array class above?
Like function parameters, we can provide defaults for some or all of them:
template <typename T = int, unsigned int size = 10>
class Array
{
// ...
};
Usage:
What about this? Definition of Stack.Array<double, 5> ar5; // Array<double, 5> Array<double> ar6; // Array<double, 10> Array<> ar7; // Array<int, 10> Array<10> ar8; // Error, type mismatch (10 is not a type) Array ar9; // Error, the Array class is a templated class
Array<Stack<int>, 20> ar10; // Stack<int> array[20]; ???
// Add a default parameter
Stack(int capacity = 10) : items_(new T[capacity]), count_(0) {}
Self-check: Given the classes below, which of the declarations are valid/invalid? If the declaration is invalid, explain why. (They are all constructor calls.)
|
|
|
Class Template Instantiation
Array<int, 10> ar; // implicit instantiation
void f(Stack<int> &s); // no instantiations, declaration
void g(Stack<int> s) // instantiates Stack<int>::dtor
{
s.Push(10); // instantiates Stack<int>::Push
}
int main()
{
Stack<int> s1(10); // instantiates Stack<int> (ctor and dtor)
Stack<int> *s2; // no instantiations (pointer)
s2 = new Stack<int>(10); // instantiates Stack<int>::ctor
f(s1); // no instantiations (by reference)
delete s2; // instantiates Stack<int>::dtor
sizeof(Stack<int>); // no method instantiations (data only)
g(s1); // instantiates copy ctor
return 0;
}
void f(Stack<int> &s) // no instantiations (reference)
{
Stack<double> t(5); // instantiates Stack<double> (ctor and dtor)
Stack<int> *ps = &s; // no instantiations (pointer)
ps->Push(10); // instantiates Stack<int>::Push
}
Somewhat advanced usage:
template Stack<int>; // instantiates all methods
|
// code.cpp #include "Stack.h" // instantiates all methods template Stack<int>; // other stuff... |
Note that you can instantiate individual methods of the class as well:main.cpp: undefined reference to `Stack::Stack[in-charge](int)' main.cpp: undefined reference to `Stack ::Push(int)' main.cpp: undefined reference to `Stack ::~Stack [in-charge]()' main.cpp: undefined reference to `Stack ::~Stack [in-charge]()'
// code.cpp #include "Stack.h" // instantiates individual methods template Stack<int>::Stack(int); // Constructor template Stack<int>::~Stack(); // Destructor template void Stack<int>::Push(int); // Push // other stuff...