There is now a mechanism which controls exactly when templates are
expanded, so that you can reduce memory usage and program size and also
instantiate them exactly once. You can control this mechanism with the
option `-fexternal-templates' and its corresponding negation
`-fno-external-templates'. Without this feature, space consumed by
template instantiations can grow unacceptably in large-scale projects
with many different source files. The default is
`-fno-external-templates'.
You do not need to use the `-fexternal-templates' option when
compiling a file that does not define and instantiate templates used in
other files, even if those files are compiled with
`-fexternal-templates'. The only side effect is an increase in
object size for each file that was compiled without
`-fexternal-templates'.
When your code is compiled with `-fexternal-templates', all
template instantiations are external; this requires that the templates
be under the control of `#pragma interface' and `#pragma
implementation'. All instantiations that will be needed should be in
the implementation file; you can do this with a typedef
that
references the instantiation needed. Conversely, when you compile using
the option `-fno-external-templates', all template instantiations are
explicitly internal.
`-fexternal-templates' also allows you to finally separate class
template function definitions from their declarations, thus speeding up
compilation times for every file that includes the template declaration.
Now you can have tens or even hundreds of lines in template
declarations, and thousands or tens of thousands of lines in template
definitions, with the definitions only going through the compiler once
instead of once for each source file. It is important to note that you
must remember to externally instantiate all templates that are
used from template declarations in interface files. If you forget to do
this, unresolved externals will occur.
In the example below, the object file generated (`example.o') will
contain the global instantiation for `Stack<int>'. If other types
of `Stack' are needed, they can be added to `example.cc' or
placed in a new file, in the same spirit as `example.cc'.
foo.h
:
#pragma interface "foo.h" template<class T> class Stack { static int statc; static T statc2; Stack() { } virtual ~Stack() { } int bar(); };
example.cc
:
#pragma implementation "foo.h" #include "foo.h" typedef Stack<int> t; int Stack<int>::statc; int Stack<int>::statc2; int Stack<int>::bar() { }Note that using `-fexternal-templates' does not reduce memory usage from completely different instantiations (`Stack<Name>' vs. `Stack<Net_Connection>'), but only collapses different occurrences of `Stack<Name>' so that only one `Stack<Name>' is generated. `-falt-external-templates' selects a slight variation in the semantics described above (incidentally, you need not specify both options; `-falt-external-templates' implies `-fexternal-templates'). With `-fexternal-templates', the compiler emits a definition in the implementation file that includes the header definition, even if instantiation is triggered from a different implementation file (e.g. with a template that uses another template). With `-falt-external-templates', the definition always goes in the implementation file that triggers instantiation. For instance, with these two header files---
`a.h': #pragma interface template <class T> class A { ... }; `b.h': #pragma interface class B { ... }; void f (A<B>);Under `-fexternal-templates', the definition of `A<B>' ends up in the implementation file that includes `a.h'. Under `-falt-external-templates', the same definition ends up in the implementation file that includes `b.h'.
You can control explicitly where a template is instantiated, without having to use the template to get an instantiation. To instantiate a class template explicitly, write `template class name<paramvals>', where paramvals is a list of values for the template parameters. For example, you might write
template class A<int>Similarly, to instantiate a function template explicitly, write `template fnsign' where fnsign is the particular function signature you need. For example, you might write
template void foo (int, int)This syntax for explicit template instantiation agrees with recent extensions to the draft ANSI standard.
The compiler's actions on ANSI-related warnings and errors have
been further enhanced. The `-pedantic-errors' option produces
error messages in a number of new situations: using return
in a
non-void
function (one returning a value); declaring a local
variable that shadows a parameter (e.g., the function takes an argument
`a', and has a local variable `a'); and use of the `asm'
keyword. Finally, the compiler by default now issues a warning when
converting from an int
to an enumerated type. This is likely to
cause many new warnings in code that hadn't triggered them before. For
example, when you compile this code,
enum boolean { false, true }; void f () { boolean x; x = 1; //assigning anyou should see the warning "int
to anenum
now triggers a warning }
anachronistic conversion from integer
type to enumeral type `boolean'
". Instead of assigning the value 1,
assign the original enumerated value `true'.
#include "list.h" main() { List< int > list1; List< float > list2; list1.add( 5 ); list2.add( 2.7 ); cout << list1.remove(); ... etc ... }
Typically, you put all your template code in a single *.h file and include it as shown above. This file should contain the class declaration and the function definitions that appear outside the class declaration.
Why all in one file? In the example above, C++ had to create code for the add(), remove(), and empty() functions for both ints and floats. To create the code, C++ needed to know the function definitions. Since C++ typically creates functions for a template class when it sees a declaration of an instance of that class, it must know the function definitions at that time. Thus, they must appear in the *.h file.
If you declare an instance of List< int > in several different *.C files, C++ will create code for add(), remove(), and empty() in each of those *.C files. It doesn't know any better, since the C++ files are compiled separately. This is wasteful.
The GNU version of C++ provides a mechanism to avoid this. Here's an except from the man page for gcc, describing one of the switches that can be included on the command line of g++:
This means that if you compile with-fexternal-templates Produce smaller code for template declarations, by generating only a single copy of each template function where it is defined (C++ only). To use this option successfully, you must also mark all files that use templates with either `#pragma implementation' (the definition) or `#pragma interface' (declarations). When your code is compiled with `-fexternal-templates', all template instan- tiations are external. You must arrange for all necessary instantiations to appear in the implementation file; you can do this with a typedef that ref- erences each instantiation needed. Conversely, when you compile using the default option `-fno-external-templates', all template instantiations are explicitly internal.
and include the lineg++ -fexternal-templates ...
in your *.h file, C++ will not create code when it encounters an instance of a template class.#pragma interface
However, the code must be created somewhere. This means that you need another *.C file (typically with the same base name as your *.h file) that causes the code to be created. Such a *.C file must contain
and must include a typedef for each type of template you need. For example, assuming the list.h file has #pragma interface in it, we cause code to be created for two types of list by compiling the following list.C file:#pragma implementation
The object file, list.o, will contain code for the functions add(), remove(), and empty() for both types of List. In the elevator assignment, you were given a complete definition of a List template class in list.h and list.C.#pragma implementation #include "list.h" typedef List< int > dummy1; typedef List< float > dummy2;