Not every function benefits from being made into a template. Likewise, it's not useful to make every class into a template. But there is one kind of class for which the C++ designers created templates: containers (aka collections in Java). Instances of container or collection classes hold and organize many data items. Think of a container as a basket into which you can place data. The type of data placed in the basket can be simple, like integers or doubles, or as complex as the most complex class you can create. The container doesn't "care."
Containers and collections represent various kinds of
data structures. "A data structure is a specialized format for organizing and storing data... Any data structure is designed to organize data to suit a specific purpose so that it can be accessed and worked with in appropriate ways. In computer programming, a data structure may be selected or designed to store data for the purpose of working on it with various algorithms." Some data structures are simple enough to be implemented directly by computer programming languages. For example, arrays, data files, structures, and classes. Other data structures are too complicated to be language primitives. These more complex data structures include stacks, lists, trees, hash tables, etc. But these complex data structures are also too useful to ignore and are provided by classes organized into language libraries.
Although the list of possible data structures is quite long, our task here is to explore the C++ syntax needed to create and to use data structures based on "templatized" classes. We begin by comparing C++ containers with Java collections.
Containers and Collections
Although C++ containers and Java collections go by different names, they provide the same services and are used to solve the same problems. Furthermore, their underlying implementations are named differently, but their syntax is nearly identical. C++ bases its containers on templates. Templates allow programmers to specify the data type of the stored data when they create the container. Similarly, Java bases its collections on generic classes, allowing programmers to specify the stored data type when creating the collection.
The main task, when using library data structures, is specifying the kind of data that the data structure will store. We'll use a data structure called a vector (C++ class name) or Vector (Java class name) to demonstrate how programmers do this. Vectors are a kind of "intelligent" array that dynamically manages their size: it can automatically grow as necessary, and programmers can shrink them when needed. But you should note that allowing a vector to grow automatically can be an expensive (in time) operation when the vectors become large. For this reason, the following examples begin with large vectors.
Each example creates a vector and inserts pseudo-random numbers into it to demonstrate how to use data structures based on templates and generic classes. Generic classes in Java can only store objects - they cannot store fundamental data types (like ints and doubles). Consequently, the random numbers in the Java program are first stored or wrapped in an instance of the Integer wrapper class. The program transparently creates the Integer wrapper objects and stores the random numbers in them; this process is called "autoboxing." Alternatively, C++ templates can store simple data types as easily as they can store instances of structures or classes.
The Java code appearing in Figure 1 is more compact and easier to follow than is the C++ version presented in Figure 2. Java has one more distinct advantage beyond compactness: portability. The code in Figure 1 will run without modification on any computer where a Java Runtime Environment (JRE) is installed. The code in Figure 2 is not as portable because it contains a system call.
An operating system provides a host environment for every program running on a computer. The operating system manages the hardware resources and provides services to the running programs. Programs can request services through system calls, which are function calls to functions defined in the host operating system. Most of the programs that we have studied so far request operating system services through language-specific library functions (e.g., operator<< and operator>>). But C++ doesn't provide a function for getting the system time. So, we must request that information directly from the operating system with a system call: _ftime_s. System calls are not standardized between operating systems, so while _ftime_s works on a Windows system, it's not appropriate for all systems. In this case, it's easy to modify the program of Figure 2 so that it runs on Unix, Linux, and macOS with only two small substitutions:
_ftime_s → ftime
_timeb → timeb
All times in milliseconds
C++
Java
Default
Optimized
ArrayList
Vector
0.026
0.019
28
44
0.028
0.019
28
44
0.027
0.020
27
44
0.027
0.019
28
44
C++ versus Java performance. Although it is often easier to write a Java program than to write the same program in C++, C++ programs typically out perform Java. The table lists the run times of the programs listed in Figures 1 and 2. The times are measured on the same computer with the same loads. Many C++ compilers implement various code-generation options; the C++ column presents the run-times for the default settings and with the speed optimizations turned to their highest settings. Each program is run four times to help eliminate any anomalous timing due to uncontrollable background processes.
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 chapter about arrays. Since then, we have used them in several examples. Most recently we created a stack class (see Figures 2 and 5) and used it to demonstrate the this pointer. Basing the stack on templates is a simple modification. The following figure illustrates the basic and most common features of a template class and lists the steps needed to convert a class to a template.
Caution
For many years, only the keyword "class" was allowed when creating template functions or template classes. Then the ANSI standard was updated to allow either "class" or "typename" when creating template functions but not when creating template classes. Newer C++ compilers (e.g., Visual Studio 2017+ and Linux) now allow both keywords to create template functions and template classes. The "class" keyword always works for both and is used in all the examples.
#include <iostream>
#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 instantiating a template class, programmers specify the data type that replaces the template variable <T> inside a pair of angle brackets. Any legal data type, including a class name, may be specified here.
Creating a stack to store integers.
Creating a stack to store person objects.
Constant Value Expressions
It is also possible to pass constant values into templates. The previous example fixed the stack size at 100 elements. This size wastes many elements when only a small stack is needed, but it fails altogether when a stack with more than 100 elements is required. A better solution is to allow the programmer using the stack to specify its size when creating it. This approach requires modifying the template code and the application code creating 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.
A template class that requires programmers to specify a data type and a stack size.
A template class with a default size. Programmers must specify a data type, but they may override or accept the default size.