Section 8 - Applying the Template to Other Types
1B.8.1 A Safe Class of Strings
The hard work is done. Now we can use this SafeArray template for other types. Let's try a SafeArray of strings. No change to the class whatsoever. No derived classes. We just change the client:
#include <sstream> int main() { int k; SafeArray<string> studentNames(5); // assign data and go out-of-bounds for (k = -3; k < 10; k++) { string stringNum; ostringstream cnvrt; cnvrt << k; stringNum = cnvrt.str(); studentNames[k] = "student # " + stringNum; } cout << "The array after some assignments:\n"; for (k = -3; k < 10; k++) cout << studentNames[k] << endl; // test the assignment operator SafeArray<string> employeeNames(40); employeeNames = studentNames; cout << "Did employeeNames[2] get assigned studentNames[2]?\n"; cout << employeeNames[2] << " " << studentNames[2] << endl; return 0; }
Here is the output:

As you see, this template easily and correctly handled the change to string type in main(). We only had to change our main() statements to assign string values, but this is to be expected.
1B.8.2 Reflection
We created a data structure called SafeArray without committing to the kind of data that it took. We declared it to be a template class. Then, we instantiated a SafeArray by declaring the type we wanted to use: float in one example and string in another. The class worked perfectly fine in either case!
We could have declared a SafeArray of iTunesEntrys with this statement;
SafeArray<iTunesEntry> tunes(SOME_SIZE);
This would enable us to use the tunes[] array without worrying about bounds checking.
1B.8.3 Header File Issues
Defining template member functions is somewhat different than defining regular class member functions. The declarations and definitions of the class template member functions should all be in the same header file. Consider the following ill-advised attempt at placing template member function implementations in a .cpp file (which is not correct!).
//file b.h -------------------- template <class T> class b { public: b(); ~b(); }; //file b.cpp -------------------- #include "b.h" template <class T> b<T>::b() { } template <class T> b<T>::~b() { } //file main.cpp -------------------- #include "b.h" void main() { b<int> bi; b<float> bf; }
When compiling b.cpp, the compiler has both the declarations and the definitions available. At this point the compiler does not need to generate any definitions for template classes, since there are no instantiations. But, when the compiler compiles main.cpp, there are two instantiations: template class B<int> and B<float>. At this point the compiler has the declarations but no definitions! This is where it becomes important to remember that the compiler needs to recreate (in a way: duplicate) the template source for each instance or invocation of the template. If there is no implementation visible (as the .cpp file contents normally are not) then it can't do that.
Typically, the template prototypes are placed at the top of the template file, and the implementation or definitions are placed below, with a #pragma once directive placed after the prototype and before the member function definitions.
1B.8.4 Using Typedef with Template Instantiation
A good programming practice is to use typedef while instantiating template classes. Then throughout the program, one can use the typedef name. There are two advantages:
- typedef's are very useful when "templates of templates" come into usage. For example, when instantiating an STL vector of int's, you could use:
typedef vector<int, allocator<int> > INTVECTOR;
Then, throughout your code, you could easily create vectors of ints like this with the shortened notation:
INTVECTOR intVec1;
- If the template definition needs to be modified in the future, simply change the typedef definition. For example, the definition of template class vector, above, required a second parameter. But if we decide to change it so that it only takes one parameter like so
typedef vector<int> INTVECTOR;
then all the previous instantiations are still correct:
INTVECTOR intVec1;
Imagine how many changes would be required if there was no typedef.