1. Template Compilation Models
When the compiler sees a template definition, it does not generate code immediately. The compiler produces type-specific instances of the template only when it sees a use of the template, such as when a function template is called or an object of a class template is defined.
Ordinarily, when we call a function, the compiler needs to see only a declaration for the function. Similarly, when we define an object of class type, the class definition must be available, but the definitions of the member functions need not be present. As a result, we put class definitions and function declarations in header files and definitions of ordinary and class-member functions in source files.
Templates are different: To generate an instantiation, the compiler must have access to the source code that defines the template. When we call a function template or a member function of a class template, the compiler needs the function definition. It needs the code we normally put in the source files.
Standard C++ defines two models for compiling template code: inclusion compilation model and separate compilation model . Class definitions and function declarations go in header files, and function and member definitions go in source files. The two models differ in how the definitions from the source files are made available to the compiler.
2. Inclusion Compilation Model
In the inclusion compilation model , the compiler must see the definition for any template that is used. Typically, we make the definitions available by adding a #include directive to the headers that declare function or class templates. That #include brings in the source file(s) that contain the associated definitions:// header file utlities.h
#ifndef UTLITIES_H
#define UTLITIES_H
template <class T> int compare(const T&, const T&);
#include "utilities.cc" // get the definitions for compare etc.
#endif
// implemenatation file utlities.cc
template <class T> int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
Some, especially older, compilers that use the inclusion model may generate multiple instantiations. If two or more separately compiled source files use the same template, these compilers will generate an instantiation for the template in each file. Ordinarily, this approach implies that a given template will be instantiated more than once. At link time, or during a prelink
phase, the compiler selects one instantiation, discarding the others. In such cases, compile-time performance can be significantly degraded if there are a lot of files that instantiate the same template. Such compilers often support mechanisms that avoid the compile-time overhead implicit in multiple instantiations of the same template. If compile time for programs using templates is too burdensome, consult your compiler’s user’s guide to see what support your compiler offers to avoid redundant instantiations.
3. Separate Compilation Model
In the separate compilation model , the compiler keeps track of the associated template definitions for us. However, we must tell the compiler to remember a given template definition. We use the export keyword to do so. A template may be defined as exported only once in a program. The declaration for this function template, should, as usual, be put in a header. The declaration must not specify export.// **.h
template<typename Type> Type sum(Type t1, Type t2);
// **.cpp
export template <typename Type>
Type sum(Type t1, Type t2) {}
Using export on a class template is a bit more complicated. As usual, the class declaration must go in a header file. The class body in the header should not use the export keyword. If we used export in the header, then that header could be used by only one source file in the program.// class template header goes in shared header file
template <class Type> class Queue { ... };
// Queue.ccimplementation file declares Queue as exported
export template <class Type> class Queue;
#include "Queue.h"
// Queue member definitions
The members of an exported class are automatically declared as exported. It is also possible to declare individual members of a class template as exported. In this case, the keyword export is not specified on the class template itself. It is specified only on the specific member definitions to be exported. The definition of exported member functions need not be visible when the member is used. The definitions of any nonexported member must be treated as in the inclusion model: The definition should be placed inside the header that defines the class template.
4. Class Template Members
Class QueueItem is a private classit has no public interface. We intend this class to be used to implement Queue and have not built it for general use. Hence, it has no public members. We’ll need to make class Queue a friend of QueueItem so that its members can access the members of QueueItem.
Inside the scope of a class template, we may refer to the class using its unqualified name.template <class Type> class QueueItem {
// private class: no public section
QueueItem(const Type &t): item(t), next(0) { }
Type item; // value stored in this element
QueueItem *next; // pointer to next element in the Queue
};
template <class Type>
class Queue
{
public:
// empty Queue
Queue(): head(0), tail(0) { }
// copy control to manage pointers to QueueItems in the Queue
Queue(const Queue &Q): head(0), tail(0)
{ copy_elems(Q); }
Queue& operator=(const Queue&);
~Queue() { destroy(); }
// return element from head of Queue
// unchecked operation: front on an empty Queue is undefined
Type& front() { return head->item; }
const Type &front() const { return head->item; }
void push(const Type &); // add element to back of Queue
void pop (); // remove element from head of Queue
bool empty () const { // true if no elements in the Queue
return head == 0;
}
private:
QueueItem<Type> *head; // pointer to first element in Queue
QueueItem<Type> *tail; // pointer to last element in Queue
// utility functions used by copy constructor, assignment, and destructor
void destroy(); // delete all the elements
void copy_elems(const Queue&); // copy elements from parameter
};
Ordinarily, when we use the name of a class template, we must specify the template parameters. There is one exception to this rule: Inside the scope of the class itself, we may use the unqualified name of the class template. For example, in the declarations of the default and copy constructor the name Queue is a shorthand notation that stands for Queue
the copy constructor definition is really equivalent to writing:Queue<Type>(const Queue<Type> &Q): head(0), tail(0)
{ copy_elems(Q); }
The compiler performs no such inference for the template parameter(s) for other templates used within the class. Hence, we must specify the type parameter when declaring pointers to the companion QueueItem class:QueueItem<Type> *head; // pointer to first element in Queue
QueueItem<Type> *tail; // pointer to last element in Queue