13.3. Template Classes

Time: 00:03:00 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)
Review

Only select operations benefit by being implemented as template functions. Similarly, only select classes benefit from a template implementation. Data structures, called containers in C++ programs, are the prime example. "A data structure is a specialized format for organizing, processing, retrieving and storing data. There are several basic and advanced types of data structures, all designed to arrange data to suit a specific purpose." Initially, C++ formed containers with multiple inheritance, inheriting "knowledge" of the container from one superclass and "knowledge" of the data from a second superclass. Today, it implements containers as classes with one or more template variables specifying the contained data's type.

Some data structures or containers are simple enough for compilers to implement them directly in computer programming languages. For example, most languages recognize arrays, data files, structures, and classes without accessing an external library. Other data structures, for example, stacks, lists, trees, and hash tables, are too complicated to be language primitives. Nevertheless, these complex data structures are too useful to ignore, so programming languages typically provide them as classes organized into language libraries. Although the list of helpful data structures is long and varied, they all utilize the same basic template syntax.

Making Template Classes

A stack is a classic data structure and a prime candidate for implementation as a template-based container. We first studied stacks in detail in the arrays chapter. Since then, we have used them in other examples, most recently as a stack class demonstrating the this pointer. Basing a stack on templates is a simple upgrade, allowing them to hold or contain any program-specified data type. The following figure illustrates the fundamental syntax and features of a template class.

Note

Historically, C++ only recognized the "class" keyword when creating templates. While the current ANSI standard allows either "class" or "typename," the original keyword often appears in data structure documentation and seems appropriate when creating containers storing objects.

#include <iostream>
using namespace std;

template <class T>
class stack
{
    private:
        static const int SIZE = 100;

        T    st[SIZE];
        int  sp = 0;

    public:
        void push(T data);
        T    pop();
        int  size();
        T    peek();
};

template <class T>
void stack<T>::push(T data)
{
    if (sp < SIZE)
        st[sp++] = data;
    else
        throw "Stack Overflow";
}

template <class T>
T stack<T>::pop()
{
    if (sp > 0)
        return st[--sp];
    else
        throw "Stack Underflow";
}

template <class T>
int stack<T>::size()
{
    return sp;
}

template <class T>
T stack<T>::peek()
{
    return st[sp - 1];
}
stack.h: a template version of the stack class. The yellow-highlighted text is "boilerplate code" activating the template mechanism and introducing the template variable, T. The template variable represents the contained data type throughout the class and its member functions. Crucially, the compiler can't compile the functions before expanding the template (i.e., replacing the template variable with the program-specified type), so programmers place the class specification and functions in a header file. The green-highlighted text becomes part of each function's name, which, after expansion, becomes part of the class name, distinguishing between functions processing different data types.
#include  <iostream>
#include  "stack.h"
using  namespace  std;

int main()
{
	stack<int>  s;

	s.push(10);
	s.push(20);
	s.push(30);

	cout << s.pop() << endl;
	cout << s.pop() << endl;
	cout << s.pop() << endl;

	return 0;
}
 
 
 
 
#include <iostream>
#include "Person.h"
#include "stack.h"
using  namespace  std;

int main()
{
	stack<Person>	p;
	Person		x("Alice");
	Person		y("Dilbert");
	Person		z("Wally");

	p.push(x);
	p.push(y);
	p.push(z);

	p.pop().display();
	p.pop().display();
	p.pop().display();

	return 0;
}
(a)(b)
Using a template class. When a C++ program creates an object from a template class, it must provide a concrete data type as part of the instantiation process. The provided data type replaces the template variable throughout the template class and member functions. The compiler compiles the code after it replaces the template variable with the concrete, program-supplied data type.
  1. Creating a stack container storing integers.
  2. Creating a stack container storing Person objects.

Constant Value Expressions

The Chapter 9 version of the stack class included a class constant, Size, specifying the maximum number of elements the container can hold. This implementation wastes space when only a small stack is needed and fails when a program calls for a large one. Chapter 6 demonstrated that programmers can specify default values for function arguments and later accept or override the defaults. Templates allow programmers the same flexibility when creating template classes. This approach requires modifying the template application code that creates the container.

Template Code
template <class T, int SIZE>
Application Code
stack<int, 10> s;
Template Code
template <class T, int SIZE = 100>
Application Code
stack<int, 10> s;
stack<int> s;
(a)(b)
Creating and using template constants.
  1. A template class that requires programmers to specify a data type and a stack size.
  2. A template class with a default size. Programmers must specify a data type but may override or accept the default size.
#include <iostream>
using namespace std;

template <class T, int SIZE = 100>
class stack
{
    private:
        T    st[SIZE];
        int  sp = 0;

    public:
        void push(T data);
        T    pop();
        int  size();
        T    peek();
};

template <class T, int SIZE>
void stack<T, SIZE>::push(T data)
{
    if (sp < SIZE)
        st[sp++] = data;
    else
        throw "Stack Overflow";
}

template <class T, int SIZE>
T stack<T, SIZE>::pop()
{
    if (sp > 0)
        return st[--sp];
    else
        throw "Stack Underflow";
}

template <class T, int SIZE>
int stack<T, SIZE>::size()
{
    return sp;
}

template <class T, int SIZE>
T stack<T, SIZE>::peek()
{
    return st[sp - 1];
}
Stack size specified by a template argument. In this version, a template variable replaces, SIZE, replaces the class constant static const int SIZE = 100;, and assigns it the default value of 100. A using program can accept or override the default value, as illustrated in the next figure. The default value, = 100, only appears in the class template statement, but the variable name, SIZE, becomes part of the function's template statements. It also becomes part of the class name, further distinguishing the function names in programs using multiple stacks containing different data types.
#include  <iostream>
#include  "stack.h"
using  namespace  std;

int main()
{
	stack<int, 10>	s;
	stack<double>	d;

	s.push(10);
	s.push(20);
	d.push(2.7);
	d.push(3.1459);

	cout << s.pop() << endl;
	cout << s.pop() << endl;
	cout << d.pop() << endl;
	cout << d.pop() << endl;

	return 0;
}
Creating an instance of a class with constant value expressions. When instantiating a template class, programs must provide a concrete type to replace the template type variable. If the template includes a constant without a default value, the instantiation must also include an appropriate value. If the template includes a default value, as in this example, programs may override the default as illustrated by the integer version. Alternatively, the program may accept the default if the template provides one, as illustrated by the double version. In this example, once expansion and compilation are complete, the program will have two distinct versions of the stack code, one for integers and another for doubles.