class Stack1
{
private:
int *items_;
int count_;
public:
Stack1(int capacity) : items_(new int[capacity]), count_(0) {};
~Stack1() { delete[] items_; }
void Push(int item) { items_[count_++] = item; }
int Pop(void) { return items_[--count_]; }
bool IsEmpty(void) { return (count_ == 0); }
};
Using the first Stack class:
|
Output:4 3 2 1 0 |
template <typename T>
class Stack2
{
private:
T *items_;
int count_;
public:
Stack2(int capacity) : items_(new T[capacity]), count_(0) {}
~Stack2() { delete[] items_; }
void Push(T item) { items_[count_++] = item; }
T Pop(void) { return items_[--count_]; }
bool IsEmpty(void) { return (count_ == 0); }
};
Template syntax:
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-1.h
StopWatch-1.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
template <typename T>
class Stack2
{
}
template <class T>
class Stack2
{
}
Stack s; // What will be instantiated? (There is no Stack class)
class IntArray1
{
public:
IntArray1(unsigned int size) : size_(size), array_(new int[size]) {}
~IntArray1() { delete [] array_; }
// ...
private:
int size_;
int *array_;
};
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(void) : size_(size) {};
// ... (no destructor needed)
private:
int size_;
int array[size]; // static array
};
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>
const int x = 2; const double d = 3.5; IntArray2<'A'> ar9; // Ok, promotion IntArray2<x> ar10; // Ok, integral conversion (signed 2 promoted to unsigned 2) IntArray2<d> ar11; // Error, double to int not allowed
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(void) : size_(size) {};
// ... (no destructor needed)
private:
int size_;
T array[size]; // static array
};
Usage:
Like function parameters, we can provide defaults for some or all of them: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]
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, Array undeclared (there's no non-template Array class)
One solution:Array<Stack<int>, 20> ar10; // Stack<int> array[20]; ???
// Add a default parameter
Stack(int capacity = 10) : items_(new T[capacity]), count_(0) {}
Array<int, 10> ar; // implicit instantiation
void f(Stack<int> &s); // no instantiations, declaration
int main(void)
{
Stack<int> s1(10); // instantiates Stack<int> (ctor and dtor)
Stack<int> *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
return 0;
}
void g(Stack<int> s) // instantiates Stack<int>::dtor
{
s.Push(10); // instantiates Stack<int>::Push
}
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
}
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(void); // Destructor template void Stack<int>::Push(int); // Push // other stuff...
template <typename T>
class Stack
{
private:
T *items_;
int count_;
public:
Stack(int capacity);
~Stack();
void Push(T item);
T Pop(void);
bool IsEmpty(void) const;
};
And the implementation into Stack.cpp:
(Note that if you define the functions outside of the class (the "normal" approach),
you need to included the template and typename keywords.)
Assume this is the implementation file, Stack.cpp:
#include "Stack.h"
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(T item) { items_[count_++] = item; }
template <typename T>
T Stack<T>::Pop(void) { return items_[--count_]; }
template <typename T>
bool Stack<T>::IsEmpty(void) const{ return (count_ == 0); }
Now we can simply include the header file and build the program:
#include "Stack.h"
int main(void)
{
const int SIZE = 5;
Stack<int> s(SIZE);
for (int i = 0; i < SIZE; i++)
s.Push(i);
while (!s.IsEmpty())
std::cout << s.Pop() << std::endl;
return 0;
}
Link errors: (from MSVC++ 6.0)
The problem is similar to inline functions (and template functions) in that the definitions are required to be available for all files that call them. There are various solutions (in order of desirability):main.obj : error LNK2001: unresolved external symbol "public: __thiscall Stack::~Stack (void)" (??1?$Stack@H@@QAE@XZ) main.obj : error LNK2001: unresolved external symbol "public: int __thiscall Stack ::Pop(void)" (?Pop@?$Stack@H@@QAEHXZ) main.obj : error LNK2001: unresolved external symbol "public: bool __thiscall Stack ::IsEmpty(void)const " (?IsEmpty@?$Stack@H@@QBE_NXZ) main.obj : error LNK2001: unresolved external symbol "public: void __thiscall Stack ::Push(int)" (?Push@?$Stack@H@@QAEXH@Z) main.obj : error LNK2001: unresolved external symbol "public: __thiscall Stack ::Stack (int)" (??0?$Stack@H@@QAE@H@Z) Debug/ClassTemplates.exe : fatal error LNK1120: 5 unresolved externals
#ifndef _STACK_H
#define _STACK_H
template <typename T>
class Stack
{
// Private and public declarations...
};
#include "Stack.cpp" // Implementation for Stack
#endif
#ifndef _STACK_H
#define _STACK_H
template <typename T>
class Stack
{
// Private and public declarations...
};
template <typename T>
Stack<T>::Stack(int capacity) : items_(new T[capacity]), count_(0)
{
}
// Other member functions...
#endif
template <typename T>
class Stack
{
private:
T *items_;
int count_;
public:
Stack(int capacity) : items_(new T[capacity]), count_(0) {}
~Stack() { delete[] items_; }
void Push(T item) { items_[count_++] = item; }
T Pop(void) { return items_[--count_]; }
bool IsEmpty(void) { return (count_ == 0); }
};
// ***** Stack.h ***** #ifndef _STACK_H #define _STACK_H // Sets up the compiler's // compliance with export #ifdef IMPLEMENTS_EXPORT #define EXPORT export #else #define EXPORT #endif EXPORT template <typename T> class Stack { private: T *items_; int count_; public: Stack(int capacity); ~Stack(); void Push(T item); T Pop(void); bool IsEmpty(void) const; }; // If not compliant, include // the implementation #ifndef IMPLEMENTS_EXPORT #include "Stack.cpp" #endif #endif // _STACK_H// ***** Stack.cpp ***** #include "Stack.h" 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(T item) { items_[count_++] = item; } template <typename T> T Stack<T>::Pop(void) { return items_[--count_]; } template <typename T> bool Stack<T>::IsEmpty(void) const { return (count_ == 0); }
To compile the program, you would simply build only main.cpp:
When these compilers support the export keyword, you would simply do this on the command-line:GNU: g++ main.cpp MSVC: cl main.cpp -EHa Borland: bcc32 main.cpp
Or if you were building within the IDE, you would just add IMPLEMENTS_EXPORT to the preprocessor definitions.GNU: g++ main.cpp -DIMPLEMENTS_EXPORT MSVC: cl main.cpp -EHs -DIMPLEMENTS_EXPORT Borland: bcc32 -DIMPLEMENTS_EXPORT main.cpp
Currently, I'm aware of only one compiler that implements export. That is the Comeau C++ Compiler.
Using a config file:
#ifndef _CONFIG_H
#define _CONFIG_H
// List compiler defines here that support export
#if defined(__COMO__)
#define IMPLEMENTS_EXPORT
#endif
#endif // _CONFIG_H
Now simply include this first:
Later, when your compiler supports export, just modify the config.h// ***** Stack.h ***** #ifndef _STACK_H #define _STACK_H #include "config.h" // ... #endif // _STACK_H
#ifndef _CONFIG_H
#define _CONFIG_H
// List compiler defines here that support export
#if defined(__COMO__) || defined(__GNUC__) || defined(_MSC_VER) || defined(__BORLANDC__)
#define IMPLEMENTS_EXPORT
#endif
#endif // _CONFIG_H
More on export: Comeau C++ Export Overview
Final Notes on Class Templates