Class Templates


Class Templates

An "old-style" stack class:
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:

int main(void)
{
  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
There are some limitations of this Stack class:

  1. No error checking (e.g. Stack may be empty when calling Pop method.)
  2. Size is kind of hard-coded (can't grow the Stack if we need more space, could be wasted unused space.)
  3. Only accepts char type.

A Stack Implementation using a Template

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:

int main(void)
{
  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(void)
{
  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(void)
{
  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-1.h
StopWatch-1.cpp

int main(void)
{
  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:

Class templates vs. Function templates:


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_; 
};
We can use the class like this:

IntArray1 ar1(20);  // Dynamic allocation at runtime (20 ints)
IntArray1 ar2(30);  // Dynamic allocation at runtime (30 ints)
Using a non-type template parameter:
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:

IntArray2<20> ar3;  // Static allocation at compile time (20 ints)
IntArray2<30> ar4;  // Static allocation at compile time (30 ints)
Notes:


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(void) : size_(size) {};
    // ... (no destructor needed)
  private:
    int size_;
    T array[size]; // static array
};
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]
Like function parameters, we can provide defaults for some or all of them:

template <typename T = int, unsigned int size = 10>
class Array
{
  // ... 
};
Usage:
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)
What about this? Definition of Stack.
Array<Stack<int>, 20> ar10; // Stack<int> array[20]; ???
One solution:
// Add a default parameter
Stack(int capacity = 10) : items_(new T[capacity]), count_(0) {}


Class Template Instantiation


Separate Compilation

Separating the interface from the implementation we put the interface in Stack.h:
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)
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
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):
  1. Export the class template using the export keyword. (Unfortunately, unavailable in most compilers.)
  2. In the header file (Stack.h) include the implementation for the member functions using an #include directive:
    #ifndef _STACK_H
    #define _STACK_H
    template <typename T>
    class Stack
    {
      // Private and public declarations...
    };
    
    #include "Stack.cpp" // Implementation for Stack
    #endif
    
  3. Implement the member functions in the same file as the class definition. (Stack.h):
    #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
    
  4. Put the definition of the member functions in the class definition in the header file:
    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); }
    };
    
More information about the different compilation models.


Using Conditional Directives with Templates

To prepare for the inevitable support for export, you can implement conditional directives:

// ***** 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:

GNU:     g++ main.cpp
MSVC:    cl main.cpp -EHa
Borland: bcc32 main.cpp
When these compilers support the export keyword, you would simply do this on the command-line:
GNU:     g++ main.cpp -DIMPLEMENTS_EXPORT
MSVC:    cl main.cpp -EHs -DIMPLEMENTS_EXPORT
Borland: bcc32 -DIMPLEMENTS_EXPORT main.cpp 
Or if you were building within the IDE, you would just add IMPLEMENTS_EXPORT to the preprocessor definitions.

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:
// ***** Stack.h *****
#ifndef _STACK_H
#define _STACK_H

#include "config.h"

// ...

#endif // _STACK_H
Later, when your compiler supports export, just modify the config.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