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:
int main() { const int SIZE = 5; Stack1 s(SIZE); for (int i = 0; i < SIZE; i++) s.Push(i); while (!s.IsEmpty()) std::cout << s.Pop() << std::endl; return 0; } |
Output:4 3 2 1 0 |
// A Stack for ints class Stack_int { public: Stack_int(int capacity) { items_ = new int[capacity]; count_ = 0 } ~Stack_int() { delete[] items_; } void Push(int item) { items_[count_++] = item; } int Pop() { return items_[--count_]; } bool IsEmpty() { return (count_ == 0); } private: int *items_; int count_; }; |
// A Stack for doubles class Stack_double { public: Stack_double(int capacity) { items_ = new double[capacity]; count_ = 0; } ~Stack_double() { delete[] items_; } void Push(double item) { items_[count_++] = item; } double Pop() { return items_[--count_]; } bool IsEmpty() { return (count_ == 0); } private: double *items_; int count_; }; |
A Stack Implementation using a Template
Class Definition | Implementations |
---|---|
template <typename T> class Stack { public: Stack(int capacity); ~Stack(); void Push(const T& item); T Pop(); bool IsEmpty(); private: T *items_; int count_; }; |
template <typename T> Stack<T>::Stack(int capacity) { items_ = new T[capacity]; count_ = 0; } template <typename T> Stack<T>::~Stack() { delete[] items_; } template <typename T> void Stack<T>::Push(const T& item) { items_[count_++] = item; } template <typename T> T Stack<T>::Pop() { return items_[--count_]; } template <typename T> bool Stack<T>::IsEmpty() { return (count_ == 0); } |
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:
int main() { const int SIZE = 5; Stack2<int> s(SIZE); // This is the only change for (int i = 0; i < SIZE; i++) s.Push(i); while (!s.IsEmpty()) std::cout << s.Pop() << std::endl; return 0; } |
Output:4 3 2 1 0 |
We can now create a Stack of doubles:
int main() { const int SIZE = 5; Stack2<double> s(SIZE); // Change type to double for (int i = 0; i < SIZE; i++) s.Push(i / 10.0); // Push a double while (!s.IsEmpty()) std::cout << s.Pop() << std::endl; return 0; } |
Output:0.4 0.3 0.2 0.1 0 |
A Stack of char *:
int main() { const int SIZE = 5; Stack2<char *> s(SIZE); // Change type to char * s.Push("One"); s.Push("Two"); s.Push("Three"); while (!s.IsEmpty()) std::cout << s.Pop() << std::endl; return 0; } |
Output:
Three Two One |
We can even create a Stack of StopWatch objects:
StopWatch.cpp
int main() { const int SIZE = 5; Stack2<StopWatch> s(SIZE); // Change type to StopWatch s.Push(StopWatch(60)); s.Push(StopWatch(90)); s.Push(StopWatch(120)); while (!s.IsEmpty()) std::cout << s.Pop() << std::endl; return 0; } |
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.)
template <typename T1 = int, int T2 = 10> class Foo { public: Foo(int x = 0) { } private: T1 items[T2]; }; |
class A { public: A() { } }; |
class B { public: B(int x) : x_(x) { } operator int() { return x_; } private: int x_; }; |
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
// main.cpp #include "Stack.h" int main() { // No instantiations Stack<int> s(10); s.Push(1); return 0; } |
// 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...