16.3. Template Compilation ModelsWhen 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. In each of these models, we structure our programs in largely the same way: 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. As of this writing, all compilers support the first model, known as the "inclusion" model; only some compilers support the second, "separate compilation" model.
Inclusion Compilation ModelIn 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 // header gaurd (Section 2.9.2, p. 69) #define UTLITIES_H template <class T> int compare(const T&, const T&); // other declarations #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; } // other definitions This strategy lets us maintain the separation of header files and implementation files but ensures that the compiler will see both files when compiling code that uses the templates. 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. This compile-time degradation is unlikely to be a problem on modern computers for many applications. However, in the context of large systems, the compile-time hit may become important. Such compilers often support mechanisms that avoid the compile-time overhead implicit in multiple instantiations of the same template. The way compilers optimize compile-time performance varies from one compiler to the next. 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. Separate Compilation ModelIn 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. The export keyword indicates that a given definition might be needed to generate instantiations in other files. A template may be defined as exported only once in a program. The compiler figures out how to locate the template definition when it needs to generate these instantiations. The export keyword need not appear on the template declaration. Ordinarily, we indicate that a function template is exported as part of its definition. We do so by including the keyword export before the template keyword:
// the template definition goes in a separately-compiled source file
export template <typename Type>
Type sum(Type t1, Type t2) /* ...*/
The declaration for this function template, should, as usual, be put in a header. The declaration must not specify export. 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. Instead, we export the class in the class implementation file: // 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.
|