Tempting C++ Templates - Security - C++ For Dummies (2014)

C++ For Dummies (2014)

Part V

Security

Chapter 26

Tempting C++ Templates

In This Chapter

arrow Examining how templates can be applied to functions

arrow Combining common functions into a single template definition

arrow Defining a template or class

arrow Implementing an initializer list for a user-defined class

The standard C++ library provides a complete set of math, time, input/output, and DOS operations, to name just a few. Many of the earlier programs in this book use the so-called character string functions defined in the include file strings. The argument types for many of these functions are fixed. For example, both arguments to strcpy(char*, char*) must be a pointer to a null-terminated character string — nothing else makes sense.

There are functions that are applicable to multiple types. Consider the example of the lowly maximum() function, which returns the maximum of two arguments. All of the following variations make sense:

int maximum(int n1, int n2); // return max of two integers
unsigned maximum (unsigned u1, unsigned u2);
double maximum (double d1, double d2);
char maximum (char c1, char c2);

I would like to implement maximum() for all four cases.

Of course, I could overload maximum() with all the possible versions:

double maximum(double d1, double d2)
{
return (d1 > d2) ? d1:d2;
}
int maximum(int n1, int n2)
{
return (n1 > n2) ? n1:n2;
}
char maximum(char c1, char c2)
{
return (c1 > c2) ? c1:c2;
}

// ...repeat for all other numeric types...

This approach works. Now C++ selects the best match, maximum(int, int), for a reference such as maximum(1, 2). However, creating the same function for each type of variable is a gross waste of time.

The source code for all the maximum(T, T) functions follows the same pattern, where T is one of the numeric types. It would be so convenient if you could write the function once and let C++ supply the type T as needed when the function is used. In fact, C++ lets you do exactly this. These so-called template definitions are the subject of this chapter.

Generalizing a Function into a Template

A function template enables you to write something that looks like a function but uses one or more type holders that C++ converts into a true type at compile time.

The following MaxTemplate program defines a template for a generic maximum() function:

// MaxTemplate - create a template max() function
// that returns the greater of two types
#include <cstdio>
#include <cstdlib>
#include <iostream>

using namespace std;

template <class T> T maximum(T t1, T t2)
{
return (t1 > t2) ? t1 : t2;
}

int main(int argc, char* pArgs[])
{
// find the maximum of two int's;
// here C++ creates maximum(int, int)
cout << "maximum(-1, 2) = "<<maximum(-1, 2) << endl;

// repeat for two doubles;
// in this case, we have to provide T explicitly since
// the types of the arguments are different
cout << "maximum(1, 2.5) = "<<maximum<double>(1, 2.5)
<< endl;

cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The keyword template is followed by angle brackets containing one or more type holders known as template parameters, each preceded by the keyword class, a constant, or both. In this case, the definition of maximum<T>(T, T) will call the “unknown type” T. Following the angle brackets is what looks like a normal function definition. In this case, the template function T maximum<T>(T t1, T t2) returns the larger of two objects t1 and t2, each of which is of type T, where T is a class to be defined later.

A template function is useless until it is converted into a real function. C++ replaces T with an actual type known as a template argument. The main() function first invokes the template definition, passing two arguments of type int. In this case, C++ can instantiate the template providingint as the definition for T.

image Creating a function from a template is called instantiating the template.

The second call is a problem — no single type can be provided for T in the template definition that matches both the int first argument and double second argument. Here the explicit reference instantiates the function maximum(double, double). C++ promotes the int argument 1 to thedouble 1.0 before making the call.

The output from this program appears as follows:

maximum(-1, 2) = 2
maximum(1, 2.5) = 2.5
Press Enter to continue...

image Be careful about terminology. For example, I used to be a hip, bad bicyclist, which is not the same thing as a bad hip bicyclist. Here's another example: A function template is not a function. The prototype for a function template is maximum<T>(T, T). The function that this template creates when T is int is the function (not function template) maximum(int, int). Your life will be easier if you remember to keep the terms straight.

Class Templates

C++ also allows the programmer to define class templates. A class template follows the same principle of using a conventional class definition with a placeholder for some unknown support classes. For example, the following TemplateVector program creates a vector for any class that the user provides. (A vector is a type of container in which the objects are stored in a row; an array is the classic vector example.)

I stored the TemplateVector class template definition in an include file called templatevector.h that appears as follows:

// TemplateVector - a simple templatized vector class
template <class T>
class TemplateVector
{
public:
TemplateVector(int nArraySize)
{
// store off the number of elements
nSize = nArraySize;
array = new T[nArraySize];
reset();
}
int size() { return nWriteIndex; }
void reset() { nWriteIndex = 0; nReadIndex = 0; }
void add(const T& object)
{
if (nWriteIndex < nSize)
{
array[nWriteIndex++] = object;
}
}
T& get()
{
return array[nReadIndex++];
}

protected:
int nSize;
int nWriteIndex;
int nReadIndex;
T* array;
};

The following TemplateVector program includes and uses that template definition:

// TemplateVector - implement a vector that uses a
// template type
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include "templatevector.h"
using namespace std;

// intFn() - manipulate a collection of integers
void intFn()
{
// create a vector of integers
TemplateVector<int> integers(10);

// add values to the vector
cout << "Enter integer values to add to a vector\n"
<< "(Enter a negative number to terminate):"
<< endl;
for(;;)
{
int n;
cin >> n;

if (n < 0) { break; }
integers.add(n);
}

cout << "\nHere are the numbers you entered:" << endl;
for(int i = 0; i < integers.size(); i++)
{
cout << i << ":" << integers.get() << endl;
}
}

// Names - create and manipulate a vector of names
class Name
{
public:
Name() = default;
Name(string s) : name(s) {}
const string& display() { return name; }
protected:
string name;
};

void nameFn()
{
// create a vector of Name objects
TemplateVector<Name> names(20);

// add values to the vector
cout << "Enter names to add to a second vector\n"
<< "(Enter an 'x' to quit):" << endl;
for(;;)
{
string s;
cin >> s;
if (s == "x" || s == "X") { break; }
names.add(Name(s));
}

cout << "\nHere are the names you entered" << endl;
for(int i = 0; i < names.size(); i++)
{
Name& name = names.get();
cout << i << ":" << name.display() << endl;
}
}

int main(int argc, char* pArgs[])
{
intFn();
nameFn();

cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The class template TemplateVector<T> contains an array of objects of class T. The class template presents two member functions: add() and get(). The add() function adds an object of class T into the next empty spot in the array. The corresponding function get() returns the next object in the array. The nWriteIndex and nReadIndex members keep track of the next empty entry and the next entry to read, respectively.

The intFn() function creates a vector of integers with room for 10 with the declaration:

TemplateVector<int> integers(10);

The program reads integer values from the keyboard, saves them off, and then spits the values back out using the functions provided by TemplateVector.

The second function, nameFn(), creates a vector of Name objects. Again, the function reads in names and then displays them back to the user.

Notice that the TemplateVector handles both int values and Name objects with equal ease. Notice also the similarity between the nameFn() and intFn() functions, even though integers and names have nothing to do with each other.

A sample session appears as follows (I’ve bolded input from the keyboard):

Enter integer values to add to a vector
(Enter a negative number to terminate):
5
10
15
-1

Here are the numbers you entered:
0:5
1:10
2:15
Enter names to add to a second vector
(Enter an 'x' to quit):
Chester
Trude
Lollie
Bodie
x

Here are the names you entered
0:Chester
1:Trude
2:Lollie
3:Bodie
Press Enter to continue...

Tips for Using Templates

You should remember a few things when using templates. First, no code is generated for a template. (Code is generated after the template is converted into a concrete class or function.) This implies that a .cpp source file is almost never associated with a class template. The entire class template definition, including all the member functions, are usually contained in an include file so that it can be available for the compiler to expand.

Second, a class template does not consume memory. Therefore, there is no penalty for creating class templates if they are never instanced. On the other hand, a class template uses memory every time it is instanced (except as noted in the next section). Thus, the code for Array<Student>consumes memory even if Array<int> already exists.

Finally, a class template cannot be compiled and checked for errors until it is converted into a real class. Thus, a program that references the class template Array<T> might compile even though Array<T> contains obvious syntax errors. The errors won’t appear until a class such asArray<int> or Array<Student> is created.

External Template Instantiations

image The TemplateVector example program instanced TemplateVector twice: once for integers and once for Name objects. Once instanced, other functions within main.cpp could refer to TemplateVector<int> without incurring any further penalty. However, suppose my program included a second source module; say, secondModule.cpp. Now suppose that secondModule.cpp also made use of TemplateVector<int>. This second module would instantiate its own copy of TemplateVector<int>. For large programs, consisting of dozens of separate modules, this could mean recompiling dozens of copies of the same code. This can mean a lot of overhead both in compile time and in the size of the resulting code.

The 2011 standard adds the keyword extern to avoid this overhead. In this example, the programmer would include the following declaration somewhere near the beginning of secondModule.cpp:

extern template class TemplateVector<int>;

This says, “don't instantiate another copy of TemplateVector<int> because some other module has already instantiated one that you can use.”

Implementing an Initializer List

Simple arrays can be initialized with an initializer list as shown here:

int myArray[] = {10, 20, 30, 40, 50};

image The 2011 standard implements a class template known as initializer_list<T> that provides the same capability to user-defined containers.

image The Macintosh version of Code::Blocks does not support initializer lists as of this writing.

C++ 2011 converts a list of objects contained within braces into a vector of class initializer_list<T>. The programmer can use this list to initialize a user-defined object. For example, the TemplateVector class in the MyVector program adds the following constructor:

class TemplateVector
{
public:
TemplateVector(const std::initializer_list<T> il) :
TemplateVector(il.size())
{
// copy the contents of il into the vector
for(const T* p = il.begin(); p < il.end(); p++)
{
add(*p);
}
}
// ...the rest of the class is the same...
};

This allows the programmer to write the following:

// MyVector - demonstrate the use of initializer list
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include "templatevector.h"
using namespace std;

int main(int argc, char* pArgs[])
{
// the following two are equivalent
// TemplateVector<int> myVector{10, 20, 30, 40, 50};
TemplateVector<int> myVector = {10, 20, 30, 40, 50};

for(int i = 0; i < myVector.size(); i++)
{
cout << i << " : " << myVector.get() << "\n";
}

cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The list {10, 20, 30, 40, 50} is passed to the TemplateVector(initializer_list<int>) constructor. That constructor first allocates a vector of length 5 and then copies the contents of the initializer list into the vector. The output of this program appears as follows:

0 : 10
1 : 20
2 : 30
3 : 40
4 : 50
Press Enter to continue...