Day 2: Templates

The capability to create parameterized types is essential to advanced programming. Because this capability can be a source of enormous confusion, and because this book relies on templates to a great extent, today you will review templates in depth. You will learn

Note: All modern C++ compilers now support templates and exceptions in one form or another. Although minor differences may exist in how exceptions are implemented, if your compiler has templates, they will work as described today. Check your documentation, however, because some older compilers do not implement templates at all (if you are working with an older compiler, you can read along but not try these exercises). In time, you may want to upgrade to a more up-to-date compiler.

Note: Visual C++ 1.52b does not support "templates." It uses a macro to implement templates and trick the compiler. Consult Technical Note #4 from the Visual C++ documentation for more information on how to work around templates in Visual C++ 2.

The Fundamentals

Templates are a built-in facility of C++ used to create parameterized types: types that change their behavior based on parameters passed in at creation. Using parameterized types is a way to reuse code safely and effectively.

New Term: Parameterized types are classes that change their behavior based on parameters passed in at creation time.

In metal- or woodworking, a template is a mold used to turn out the same pattern again and again. A C++ template tells the compiler how to create a type of object, substituting the parameters you provide at compile time. A list template, for example, tells the compiler how to create a list; then, at compile time you can specify a list of car parts, a list of children, a list of numbers, and a list of things to do. The template encapsulates the list-specific capabilities, and the parameters apply the list to different types of objects.

Unlike macros, templates are a built-in part of the language. Templates are type-safe, and parameterized types can be used wherever other types can be used.

Template Instances

Each time you declare an instance of a template, the compiler creates a declaration for the class and instantiates an actual object. These objects can be used like any other objectas a parameter to a function, as a return value, and so on.

Template classes can declare three types of friend functions: nontemplate, general template, and type-specific template. A template can declare static data members, in which case each instance of the template has its own set of static data.

Note: Templates are used throughout this book, so it is essential that you are fully comfortable with them.

Template Definition

You can declare a parameterized array object (a template for an array) by using the following code:

    1:     template <class T>  // declare the template and the parameter
    2:     class Array            // the class being parameterized
    3:     {
    4:     public:
    5:       Array();
    6:      // full class declaration here
    7:      };

The keyword template is used at the beginning of every declaration and definition of a Template class. After the keyword template are the parameters of the template. The parameters are the items that will change with each instance. In the array template shown here, for example, the type of the objects stored in the array will change; one instance might store an array of integers, and another might store an array of controls (list boxes, buttons, and so on) in a window.

In this example, the keyword class is used, followed by the identifier T. The keyword class indicates that this parameter is a type. The identifier T is used throughout the rest of the template definition to refer to the parameterized type. One instance of this class will substitute int everywhere T appears; another will substitute Window.

To declare an int and a Window instance of the Parameterized Array class, use the following code:

    Array<int> anIntArray;
    Array<Window> aWindowArray;

The object anIntArray is of the type array of integer s; the object aWindowArray is of the type array of Windows. You now can use the type Array<int> anywhere you normally would use a type as the return value from a function, as a parameter to a function, and so on. Listing 2.1 provides the full declaration of this stripped-down array template.

Note: Although Microsoft's 32-bit (Visual C++ 2.0) compiler does support templates, the Microsoft 16-bit compiler used with DOS and Windows 3.1 (Visual C++ 1.5) does not support templates. Many other compilers for Windows 3.1 do support templates, however.

Listing 2.1 A Template of an Array Class

    1:     #include <iostream.h>
    2:
    3:     const int DefaultSize = 10;
    4:
    5:     template <class T>  // declare the template and the parameter
    6:     class Array            // the class being parameterized
    7:     {
    8:     public:
    9:        // constructors
    10:       Array(int itsSize = DefaultSize);
    11:       Array(const Array &rhs);
    12:       ~Array() { delete [] pType; }
    13:
    14:       // operators
    15:       Array& operator=(const Array&);
    16:       Type& operator[](int offSet) { return pType[offSet]; }
    17:
    18:       // accessors
    19:       int getSize() { return itsSize; }
    20:
    21:    private:
    22:       Type *pType;
    23:       int  itsSize;
    24:    };

Output:

None.

Analysis:

The definition of the template begins in line 5 with the keyword template followed by the parameter. In this case, the parameter is identified to be a type, by the keyword class, and the identifier T is used.

From line 6 until the end of the template in line 24, the rest of the declaration is like any other class declaration except that wherever the type of the object normally would appear, the identifier T is used instead. For example, operator[] would be expected to return a reference to an object in the array and, in fact, it is declared to return a reference to a T.

When an instance of an int array is declared, the operator= provided to that array will return a reference to an integer. When an instance of an Animal array is declared, the operator= provided to the Animal array will return a reference to an Animal.

Using the Name

Within the class declaration, the word Array may be used without further qualification. Elsewhere in the program, this class will be referred to as Array<T>. If you do not write the constructor within the class declaration, for example, you must write an implementation as shown in listing 2.2.

Listing 2.2 An Array Class Constructor Template Definition

    1:  template <class T>
    2:  Array<T>::Array(int size):
    3:  itsSize = size
    4:  {
    5:    pType = new T[size];
    6:    for (int i = 0; i<size; i++)
    7:        pType[i] = 0;
    8:  }

Output:

None.

Analysis:

The declaration in line 1 is required to identify the type (class T). The template name is Array<T> and the function name is Array(int size).

The remainder of the function is exactly the same as it would be for a nontemplate function. This is a common and preferred method to get the class and its functions working as a simple declaration before turning it into a template.

Implementing the Template

The full implementation of the Template class array requires implementing the copy constructor, operator=, and so on. Listing 2.3 provides a simple driver program to exercise this Template class.

Listing 2.3 Implementation of the Template Array

    1:     #include <iostream.h>
    2:
    3:     const int DefaultSize = 10;
    4:
    5:     // declare a simple Animal class so that we can
    6:     // create an array of animals
    7:
    8:     class Animal
    9:     {
    10:    public:
    11:       Animal(int);
    12:       Animal();
    13:       ~Animal() {}
    14:       int GetWeight() const { return itsWeight; }
    15:       void Display() const { cout << itsWeight; }
    16:    private:
    17:       int itsWeight;
    18:    };
    19:
    20:    Animal::Animal(int weight):
    21:    itsWeight(weight)
    22:    {}
    23:
    24:    Animal::Animal():
    25:    itsWeight(0)
    26:    {}
    27:
    28:
    29:    template <class T>  // declare the template and the parameter
    30:    class Array            // the class being parameterized
    31:    {
    32:    public:
    33:       // constructors
    34:       Array(int itsSize = DefaultSize);
    35:       Array(const Array &rhs);
    36:       ~Array() { delete [] pType; }
    37:
    38:       // operators
    39:       Array& operator=(const Array&);
    40:       T& operator[](int offSet) { return pType[offSet]; }
    41:       const T& operator[](int offSet) const { return pType[offSet]; }
    42:
    43:       // accessors
    44:       int GetSize() const { return itsSize; }
    45:
    46:    private:
    47:       T *pType;
    48:       int  itsSize;
    49:    };
    50:
    51:    // implementations follow...
    52:
    53:    // implement the constructor
    54:    template <class T>
    55:    Array<T>::Array(int size):
    56:    itsSize(size)
    57:    {
    58:       pType = new T[size];
    59:       for (int i = 0; i<size; i++)
    60:          pType[i] = 0;
    61:    }
    62:
    63:    // copy constructor
    64:    template <class T>
    65:    Array<T>::Array(const Array &rhs)
    66:    {
    67:       itsSize = rhs.GetSize();
    68:       pType = new T[itsSize];
    69:       for (int i = 0; i<itsSize; i++)
    70:          pType[i] = rhs[i];
    71:    }
    72:
    73:    // operator=
    74:    template <class T>
    75:    Array<T>& Array<T>::operator=(const Array &rhs)
    76:    {
    77:       if (this == &rhs)
    78:          return *this;
    79:       delete [] pType;
    80:       itsSize = rhs.GetSize();
    81:       pType = new T[itsSize];
    82:       for (int i = 0; i<itsSize; i++)
    83:          pType[i] = rhs[i];
    84:       return *this;
    85:    }
    86:
    87:    // driver program
    88:    void main()
    89:    {
    90:       Array<int> theArray;      // an array of integers
    91:       Array<Animal> theZoo;     // an array of Animals
    92:       Animal *pAnimal;
    93:
    94:       // fill the arrays
    95:       for (int i = 0; i < theArray.GetSize(); i++)
    96:       {
    97:          theArray[i] = i*2;
    98:          pAnimal = new Animal(i*3);
    99:          theZoo[i] = *pAnimal;
    100:      }
    101:
    102:      // print the contents of the arrays
    103:      for (int j = 0; j < theArray.GetSize(); j++)
    104:      {
    105:         cout << "theArray[" << j << "]:\t" << theArray[j] << "\t\t";
    106:         cout << "theZoo[" << j << "]:\t";
    107:         theZoo[j].Display();
    108:         cout << endl;
    109:      }
    110:   }

Output:

    theArray[0]:    0            theZoo[0]:    0
    theArray[1]:    2            theZoo[1]:    3
    theArray[2]:    4            theZoo[2]:    6
    theArray[3]:    6            theZoo[3]:    9
    theArray[4]:    8            theZoo[4]:    12
    theArray[5]:    10           theZoo[5]:    15
    theArray[6]:    12           theZoo[6]:    18
    theArray[7]:    14           theZoo[7]:    21
    theArray[8]:    16           theZoo[8]:    24
    theArray[9]:    18           theZoo[9]:    27

Analysis:

Lines 8 through 26 provide a minimal Animal class, created here so that there are objects of a user-defined type to add to the array. In lines 11 and 12, the constructors are declared; in line 13, the destructor is defined inline.

Lines 14 and 15 provide simple inline accessor functions, and lines 20 through 26 provide the implementations to the constructors.

Line 29 starts the template definition of the Parameterized Array class. The keyword template in line 29 declares that what follows is a template, and that the parameter to the template is a type, designated as T. The array class has two constructors as shown, the first of which takes a size and defaults to the constant int DefaultSize.

The assignment and offset operators are declared (lines 39 and 40), with the latter declaring both a const and a non-const variant. The only accessor provided is GetSize(), which returns the size of the array (line 44).

One certainly can imagine a fuller interface, and for any serious Array program, what has been supplied would be inadequate. A minimum requirement would be operators to remove elements, to expand the array, to pack the array, and so on.

The private data consists of the size of the array and a pointer to the actual in-memory array of objects.

The implementation of the parameterized methods follows, beginning in line 54. Note that each function begins with the template keyword, and everywhere the type would be used (Animal, in this case), the designation T is used instead.

The type of the Array can be thought of as ArrayOfAnimal or ArrayOfInt. The designation for this is Array<T>, which the compiler treats as Array<Animal> or Array<int> at runtime. Note that you cannot write Array<Animal> in your code this is for illustration purposes only.

Note also that T becomes a synonym for the type. So that you see in line 47 that the pointer is declared to be a pointer to T, you allocate memory in line 68 by writing new T[].

In lines 90 and 91 in the driver program, two instances of the parameterized Array type are declared. The first, in line 90, is an array of integers (Array<int>). This designates that everywhere the template had T, it now should use int.

In line 91, an array of the user-defined Animal type is declared, which works in exactly the same way as the array of int works.

In line 97, theArray, which is an array of integers, is filled with integers. In line 99, theZoo, which is an array of Animals, is initialized with Animal objects.

In line 105, the members of the integer array theArray each are accessed. In line 107, the members of the Animal array, theZoo, are accessed in turn using the same parameterized offset accessor!

DO and DON'T: Working with Parameterized Types

DO use parameterized types (templates) to create flexible containers and functions.

DO remember to use the parameterized type wherever the actual type should be used.

DON'T forget that at compile time, each instance of the parameterized type will be created by the compiler, just as if you had written it explicitly.

DO use templates to provide code reuse and type-safe flexibility in your programs.

DO consider using templates rather than inheritance, where applicable.

Template Functions

If you want to pass an array object to a function, you must pass a particular instance of the array--not a template. If SomeFunction() takes an integer array as a parameter, therefore, you may write

    void SomeFunction(Array<int>&);    // ok

but you may not write

    void SomeFunction(Array<T>&);   // error!

because there is no way to know what a T& is. You also may not write

    void SomeFunction(Array &);     // error!

because there is no class Arraythere just are the template and the instances.

To accomplish the more general approach, you must declare a function template:

    template <class T>
    void MyTemplateFunction(Array<T>&);    // ok

Here, the function MyTemplateFunction() is declared to be a template function by the declaration in the preceding line. Note that template functions can have any name, just as other functions can.

Template functions also can take instances of the template, in addition to the parameterized form. Here's an example:

    template <class T>
    void MyOtherFunction(Array<T>&, Array<int>&);    // ok

Note that this function takes two arrays: a parameterized array and an array of integers. The former can be an array of any object, but the latter is always an array of integers.

Using Template Items

You can treat template items as you would any other type. You can pass them as parameters, either by reference or by value, and you can return them as the return values of functions, also by value or by reference. Listing 2.4 demonstrates passing template objects.

Listing 2.4 Passing Template Objects to and from Functions

    1:       // Listing 2.4 - Passing Template Objects to and from Functions
    2:
    3:       #include <iostream.h>
    4:
    5:       const int DefaultSize = 10;
    6:
    7:       // A trivial class for adding to arrays
    8:       class Animal
    9:       {
    10:      public:
    11:      // constructors
    12:           Animal(int);
    13:           Animal();
    14:           ~Animal();
    15:
    16:           // accessors
    17:           int GetWeight() const { return itsWeight; }
    18:           void SetWeight(int theWeight) { itsWeight = theWeight; }
    19:
    20:            // friend operators
    21:           friend ostream& operator<< (ostream&, const Animal&);
    22:
    23:      private:
    24:           int itsWeight;
    25:      };
    26:
    27:      // extraction operator for printing animals
    28:      ostream& operator<< (ostream& theStream, const Animal& theAnimal)
    29:      {
    30:      theStream << theAnimal.GetWeight();
    31:      return theStream;
    32:      }
    33:
    34:      Animal::Animal(int weight):
    35:      itsWeight(weight)
    36:      {
    37:         // cout << "Animal(int)\n";
    38:      }
    39:
    40:      Animal::Animal():
    41:      itsWeight(0)
    42:      {
    43:         // cout << "Animal()\n";
    44:      }
    45:
    46:      Animal::~Animal()
    47:      {
    48:        // cout << "Destroyed an animal...\n";
    49:      }
    50:
    51:      template <class T>  // declare the template and the parameter
    52:      class Array            // the class being parameterized
    53:      {
    54:      public:
    55:         Array(int itsSize = DefaultSize);
    56:         Array(const Array &rhs);
    57:         ~Array() { delete [] pType; }
    58:
    59:         Array& operator=(const Array&);
    60:         T& operator[](int offSet) { return pType[offSet]; }
    61:         const T& operator[](int offSet) const { return pType[offSet]; }
    62:         int GetSize() const { return itsSize; }
    63:
    64:
    65:      private:
    66:         T *pType;
    67:         int  itsSize;
    68:      };
    69:
    70:      template <class T>
    71:      ostream& operator<< (ostream& output, const Array<T>& theArray)
    72:      {
    73:         for (int i = 0; i<theArray.GetSize(); i++)
    74:            output << "[" << i << "] " << theArray[i] << endl;
    75:         return output;
    76:      }
    77:     // implement the constructor
    78:     template <class T>
    79:     Array<T>::Array(int size):
    80:     itsSize(size)
    81:     {
    82:        pType = new T[size];
    83:        for (int i = 0; i<size; i++)
    84:           pType[i] = 0;
    85:     }
    86:
    87:     // copy constructor
    88:     template <class T>
    89:     Array<T>::Array(const Array &rhs)
    90:     {
    91:        itsSize = rhs.GetSize();
    92:        pType = new T[itsSize];
    93:        for (int i = 0; i<itsSize; i++)
    94:           pType[i] = rhs[i];
    95:     }
    96:
    97:     // operator=
    98:    template <class T>
    99:    Array<T>& Array<T>::operator=(const Array &rhs)
    100:    {
    101:       if (this == &rhs)
    102:          return *this;
    103:       delete [] pType;
    104:       itsSize = rhs.GetSize();
    105:       pType = new T[itsSize];
    106:       for (int i = 0; i<itsSize; i++)
    107:          pType[i] = rhs[i];
    108:       return *this;
    109:    }
    110:
    111:
    112:
    113:     void IntFillFunction(Array<int>& theArray);
    114:     void AnimalFillFunction(Array<Animal>& theArray);
    115:     enum BOOL {FALSE, TRUE};
    116:
    117:     void main()
    118:     {
    119:        Array<int> intArray;
    120:        Array<Animal> animalArray;
    121:        IntFillFunction(intArray);
    122:        AnimalFillFunction(animalArray);
    123:        cout << "intArray...\n" << intArray;
    124:        cout << "\nanimalArray...\n" << animalArray << endl;
    125:     }
    126:
    127:     void IntFillFunction(Array<int>& theArray)
    128:     {
    129:        BOOL Stop = FALSE;
    130:        int offset, value;
    131:        while (!Stop)
    132:        {
    133:           cout << "Enter an offset (0-9) and a value. (-1 to stop): " ;
    134:           cin >> offset >> value;
    135:           if (offset < 0)
    136:              break;
    137:           if (offset > 9)
    138:           {
    139:              cout << "***Please use values between 0 and 9.***\n";
    140:              continue;
    141:           }
    142:           theArray[offset] = value;
    143:        }
    144:     }
    145:
    146:
    147:     void AnimalFillFunction(Array<Animal>& theArray)
    148:     {
    149:        Animal * pAnimal;
    150:        for (int i = 0; i<theArray.GetSize(); i++)
    151:        {
    152:           pAnimal = new Animal;
    153:           pAnimal->SetWeight(i*100);
    154:           theArray[i] = *pAnimal;
    155:           delete pAnimal;  // a copy was put in the array
    156:        }
    157:     }
Output:
    Enter an offset (0-9) and a value. (-1 to stop): 1 10
    Enter an offset (0-9) and a value. (-1 to stop): 2 20
    Enter an offset (0-9) and a value. (-1 to stop): 3 30
    Enter an offset (0-9) and a value. (-1 to stop): 4 40
    Enter an offset (0-9) and a value. (-1 to stop): 5 50
    Enter an offset (0-9) and a value. (-1 to stop): 6 60
    Enter an offset (0-9) and a value. (-1 to stop): 7 70
    Enter an offset (0-9) and a value. (-1 to stop): 8 80
    Enter an offset (0-9) and a value. (-1 to stop): 9 90
    Enter an offset (0-9) and a value. (-1 to stop): 10 10
    ***Please use values between 0 and 9. ***
    Enter an offset (0-9) and a value. (-1 to stop): -1 -1

    intArray:...
    [0] 0
    [1] 10
    [2] 20
    [3] 30
    [4] 40
    [5] 50
    [6] 60
    [7] 70
    [8] 80
    [9] 90

    animalArray:...
    [0] 0
    [1] 100
    [2] 200
    [3] 300
    [4] 400
    [5] 500
    [6] 600
    [7] 700
    [8] 800
    [9] 900

Analysis:

The Animal class is declared in lines 7 through 24. Although this is a minimal class declaration, it does provide its own insertion operator (<<) to allow printing of the animal's weight.

Note that Animal has a default constructor, declared in line 13 and defined in lines 40 through 44. This is necessary because when you add an object to an array, its default constructor is used to create the object. This creates some difficulties, as you will see.

In line 113, the function IntFillFunction() is declared. The prototype indicates that this function takes an integer array. Note that this is not a template function; it expects only one type of an array: an integer array. Similarly, in line 114, AnimalFillFunction() is declared to take an array of Animals.

The implementations for these functions are different from one another, because filling an array of integers does not have to be accomplished in the same way as filling an array of Animals.

The implementation for IntFillFunction() is provided in lines 127 through 144. The user is prompted for a value, and that value is placed directly into the array.

In lines 147 through 157, the AnimalFillFunction() is defined, and this is quite different. The value provided by the user cannot be placed directly into the array. Instead, in line 152, a new Animal is created, and its weight is set to the value given by the user in line 153. The animal then is placed into the array, and the temporary pointer is deleted because it no longer is needed.

In a real-world program, you might consider having an array of pointers to Animals (or other large objects), rather than having an array of the objects themselves. This method can save significant stack space if the array is created on the stack.

Specialized Functions

If you uncomment the print statements in Animal's constructors and destructor (lines 37 and 43 in listing 2.4); you will find that there are unanticipated extra constructions and destructions of Animals.

When an object is added to an array, its default constructor is called. The Array constructor, however, goes on to assign 0 to the value of each member of the array, as shown in lines 85 and 86 of listing 2.4.

When you write someAnimal = (Animal) 0; you call the default operator= for Animal. This causes a temporary Animal object to be created, using the constructor that takes an integer (zero). That temporary object is used on the right-hand side of the operator equals, and then is destroyed.

This is an unfortunate waste of time; the Animal object already was initialized properly. You cannot remove this line, however, because integers are not automatically initialized to value 0.

The solution is to teach the template not to use this constructor for Animals, but to use a special Animal constructor.

You can provide an explicit implementation for the Animal class, as indicated in listing 2.5.

Listing 2.5 Specializing Template Implementations

    1:     #include <iostream.h>
    2:
    3:     const int DefaultSize = 3;
    4:
    5:     // A trivial class for adding to arrays
    6:       class Animal
    7:       {
    8:       public:
    9:        // constructors
    10:         Animal(int);
    11:         Animal();
    12:         ~Animal();
    13:
    14:         // accessors
    15:         int GetWeight() const { return itsWeight; }
    16:         void SetWeight(int theWeight) { itsWeight = theWeight; }
    17:
    18:          // friend operators
    19:         friend ostream& operator<< (ostream&, const Animal&);
    20:
    21:      private:
    22:         int itsWeight;
    23:      };
    24:
    25:       // extraction operator for printing animals
    26:      ostream& operator<< (ostream& theStream, const Animal& theAnimal)
    27:      {
    28:       theStream << theAnimal.GetWeight();
    29:       return theStream;
    30:      }
    31:
    32:      Animal::Animal(int weight):
    33:      itsWeight(weight)
    34:      {
    35:         cout << "animal(int)\n";
    36:      }
    37:
    38:      Animal::Animal():
    39:      itsWeight(0)
    40:      {
    41:         cout << "animal()\n";
    42:      }
    43:
    44:      Animal::~Animal()
    45:      {
    46:        cout << "Destroyed an animal...\n";
    47:      }
    48:
    49:    template <class T>  // declare the template and the parameter
    50:    class Array            // the class being parameterized
    51:    {
    52:    public:
    53:       // constructors
    54:       Array(int itsSize = DefaultSize);
    55:       Array(const Array &rhs);
    56:       ~Array() { delete [] pType; }
    57:
    58:       // operators
    59:       Array& operator=(const Array&);
    60:       T& operator[](int offSet) { return pType[offSet]; }
    61:       const T& operator[](int offSet) const { return pType[offSet]; }
    62:
    63:       // accessors
    64:       int GetSize() const { return itsSize; }
    65:
    66:
    67:    private:
    68:       T *pType;
    69:       int  itsSize;
    70:    };
    71:
    72:    template <class T>
    73:    Array<T>::Array(int size):
    74:    itsSize(size)
    75:    {
    76:       pType = new T[size];
    77:       for (int i = 0; i<size; i++)
    78:         pType[i] = (T)0;
    79:    }
    80:
    81:    template <class T>
    82:    Array<T>& Array<T>::operator=(const Array &rhs)
    83:    {
    84:       if (this == &rhs)
    85:          return *this;
    86:       delete [] pType;
    87:       itsSize = rhs.GetSize();
    88:       pType = new T[itsSize];
    89:       for (int i = 0; i<itsSize; i++)
    90:          pType[i] = rhs[i];
    91:       return *this;
    92:    }
    93:
    94:    template <class T>
    95:    Array<T>::Array(const Array &rhs)
    96:    {
    97:       itsSize = rhs.GetSize();
    98:      pType = new T[itsSize];
    99:      for (int i = 0; i<itsSize; i++)
    100:         pType[i] = rhs[i];
    101:   }
    102:
    103:   template <class T>
    104:   ostream& operator<< (ostream& output, const Array<T>& theArray)
    105:   {
    106:      for (int i = 0; i<theArray.GetSize(); i++)
    107:         output << "[" << i << "] " << theArray[i] << endl;
    108:      return output;
    109:   }
    110:
    111:   template<classT>
    112:   Array<Animal>::Array(int AnimalArraySize):
    113:   itsSize(AnimalArraySize)
    114:   {
    115:      pType = new T[AnimalArraySize];
    116:   }
    117:
    118:
    119:   void IntFillFunction(Array<int>& theArray);
    120:   void AnimalFillFunction(Array<Animal>& theArray);
    121:   enum BOOL {FALSE, TRUE};
    122:
    123:   void main()
    124:   {
    125:      Array<int> intArray;
    126:      Array<Animal> animalArray;
    127:      IntFillFunction(intArray);
    128:      AnimalFillFunction(animalArray);
    129:      cout << "intArray...\n" << intArray;
    130:      cout << "\nanimalArray...\n" << animalArray << endl;
    131:   }
    132:
    133:   void IntFillFunction(Array<int>& theArray)
    134:   {
    135:      BOOL Stop = FALSE;
    136:      int offset, value;
    137:      while (!Stop)
    138:      {
    139:         cout << "Enter an offset (0-9) and a value. (-1 to stop): " ;
    140:         cin >> offset >> value;
    141:         if (offset < 0)
    142:            break;
    143:         if (offset > 2)
    144:         {
    145:            cout << "***Please use values between 0 and 2.***\n";
    146:            continue;
    147:         }
    148:         theArray[offset] = value;
    149:      }
    150:   }
    151:
    152:
    153:   void AnimalFillFunction(Array<Animal>& theArray)
    154:   {
    155:      Animal * pAnimal;
    156:      for (int i = 0; i<theArray.GetSize(); i++)
    157:      {
    158:         pAnimal = new Animal(i*10);
    159:         theArray[i] = *pAnimal;
    160:         delete pAnimal;
    161:      }
    162:   }

Note: Line numbers have been added to the input to make analysis easier. Your output will not have these line numbers.

Output:

    1:     animal()
    2:     animal()
    3:     animal()
    4:     Enter an offset (0-9) and a value. (-1 to stop): 0 0
    5:     Enter an offset (0-9) and a value. (-1 to stop): 1 1
    6:     Enter an offset (0-9) and a value. (-1 to stop): 2 2
    7:     Enter an offset (0-9) and a value. (-1 to stop): 3 3
    ***Please use values between 0 and 2.***
    8:     Enter an offset (0-9) and a value. (-1 to stop): -1 -1
    9:     animal(int)
    10:    Destroyed an animal...
    11:    animal(int)
    12:    Destroyed an animal...
    13:    animal(int)
    14:    Destroyed an animal...
    15:    intArray...
    16:    [0] 0
    17:    [1] 1
    18:    [2] 2
    19:
    20:    animalArray...
    21:    [0] 0
    22:    [1] 10
    23:    [2] 20
    24:
    25:    Destroyed an animal...
    26:    Destroyed an animal...
    27:    Destroyed an animal...
    28:
    29:    << second run >>
    30:
    31:    animal()
    32:    animal()
    33:    animal()
    34:    animal(int)
    35:    Destroyed an animal...
    36:    animal(int)
    37:    Destroyed an animal...
    38:    animal(int)
    39:    Destroyed an animal...
    40:    Enter an offset (0-9) and a value. (-1 to stop): 0 0
    41:    Enter an offset (0-9) and a value. (-1 to stop): 1 1
    42:    Enter an offset (0-9) and a value. (-1 to stop): 2 2
    ***Please use values between 0 and 2.***
    43:    Enter an offset (0-9) and a value. (-1 to stop): 3 3
    44:    Enter an offset (0-9) and a value. (-1 to stop): -1 -1
    45:    animal(int)
    46:    Destroyed an animal...
    47:    animal(int)
    48:    Destroyed an animal...
    49:    animal(int)
    50:    Destroyed an animal...
    51:    intArray...
    52:    [0] 0
    53:    [1] 1
    54:    [2] 2
    55:
    56:    animalArray...
    57:    [0] 0
    58:    [1] 10
    59:    [2] 20
    60:
    61:    Destroyed an animal...
    62:    Destroyed an animal...
    63:    Destroyed an animal...

Analysis:

Listing 2.5 reproduces both classes in their entirety so that you can see the creation and destruction of temporary Animal objects. The value of DefaultSize has been reduced to 3 to simplify the output.

The Animal constructors and destructors in lines 32 through 47 each print a statement indicating when they are called.

In lines 72 through 79, the template behavior of an Array constructor is declared. In lines 112 through 116, the specialized constructor for an Array of Animals is demonstrated. Note that in this special constructor, the default constructor is allowed to set the initial value for each animal, and no explicit assignment is performed.

The first time this program is run, the first set of output is shown. Lines 1 through 3 of the output show the three default constructors called by creating the array. The user enters three numbers, and these are entered into the integer array.

Execution jumps to AnimalFillFunction(). Here, a temporary animal is created on the heap in line 158, and its value is used to modify the Animal object in the array in line 159. In line 160, the temporary Animal is destroyed. This process is repeated for each member of the array, and is reflected in the output in lines 9 through 15.

At the end of the program, the arrays are destroyed; and when their destructors are called, all their objects are destroyed as well. This is reflected in the output in lines 25 through 27.

For the second set of output (lines 31 through 63), the special implementation of the Array of character constructor (shown in lines 112 through 116 of the program) is commented out. When the program is run again, the template constructor (shown in lines 72 through 79 of the program) is run when the Animal array is constructed.

This process causes temporary Animal objects to be called for each member of the array in lines 77 and 78 of the program and is reflected in the output in lines 26 through 29 of the output.

In all other respects, the output for the two runs is identical, as you would expect.

Summary

Today you learned how to create and use templates. Templates are a built-in facility of C++ to create parameterized types that change their behavior based on parameters passed in at creation. These types are a way to reuse code safely and effectively.

The definition of the template determines the parameterized type. Each instance of the template is an actual object, which can be used like any other objectas a parameter to a function, as a return value, and so on.

Template classes can declare three types of friend functions: nontemplate, general template, and type-specific template. A template can declare static data membersin which case, each instance of the template has its own set of static data.

If you need to specialize behavior for some template functions based on the actual type, you can override a template function with a particular type. This process works for member functions as well.

Q&A

Q: Why use templates when macros will do?

A: Templates are type-safe and built into the language.

Q: What is the difference between the parameterized type of a template function and the parameters to a normal function?

A: A regular function (nontemplate) takes parameters on which it may take action. A template function enables you to parameterize the type of a particular parameter to the function. In other words, you can pass an array of type to a function, and then have the type determined by the template instance.

Q: When do you use templates and when do you use inheritance?

A: Use templates when all the behavior or virtually all the behavior is unchanged, except in regard to the type of the item on which your class acts. If you find yourself copying a class and changing only the type of one or more of its members, it may be time to consider using a template.

Q: When do you use general Template Friend classes?

A: You use general Template Friend classes when every instance, regardless of type, should be a friend to this class or function.

Q: When do you use type-specific Template Friend classes or functions?

A: You use these classes or functions when you want to establish a one-to-one relationship between two classes. For example, array<int> should match iterator<int> but not iterator<Animal>.

Q: Are templates portable?

A: Yes, templates are portable across all modern C++ compilers.

Q: Do all compilers support templates?

A: Nearly all new compilers support templates. Templates have been a part of the language since C++ 2.0, and are an accepted part of the emerging ANSI/ISO specification.

Workshop

The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you have learned. Try to answer the quiz and exercise questions before checking the answers in Appendix A, and make sure that you understand the answers before continuing to the next chapter.

Quiz

  1. What is the difference between a template and a macro?

  2. What is the difference between the parameter in a template and the parameter in a function?

  3. What is the difference between a type-specific Template Friend class and a general Template Friend class?

  4. Is it possible to provide special behavior for one instance of a template but not for other instances?

  5. How many static variables are created if you put one static member into a Template class definition?

[Click here for Answers]

Exercises

  1. Create a template based on this List class:
        class List
        {
        private:
    
        public:
            List():head(0),tail(0),theCount(0) {}
            virtual ~List();
    
            void insert( int value );
            void append( int value );
            int is_present( int value ) const;
            int is_empty() const { return head == 0; }
            int count() const { return theCount; }
        private:
            class ListCell
            {
            public:
                ListCell(int value, ListCell *cell = 0)
                :val(value),next(cell){}
                int val;
                ListCell *next;
            };
            ListCell *head;
            ListCell *tail;
            int theCount;
        };
    

  2. Write the implementation for the List class (nontemplate) version.

  3. Write the template version of the implementations.

  4. Declare three list objects: a list of strings, a list of windows, and a list of integers.

  5. BUG BUSTER: What is wrong with the following code (assume that the List template is defined and Window is the class defined earlier in the book):
        List<Window> Window_List;
        Window Felix;
        WindowList.append( Felix );
        cout << "Felix is " <<
            ( Window_List.is_present( Felix ) ) ? "" : "not " <<
            "present\n";
    

    Tip: HINT (this is tough): What makes the Window type different from int?

  6. Declare friend operator==for List.

  7. Implement friend operator==for List.

  8. Does operator==have the same problem as in question 5?

  9. Implement a template function for "swap" that exchanges two variables.

Go to: Table of Contents | Next Page