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.
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.
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]; }
#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) |
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.
|
|
(a) | (b) |
#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]; }
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; }