|
|
|
Templates are useful when implementing generic constructs like vectors, stacks,
lists, and queues, which can be used with any arbitrary type. C++ templates provide
a way to reuse source code as opposed to inheritance and composition which provide
a way to reuse object code.
This section is organized into the following topics:
|
| Templates |
|
A template defines a group of classes or functions. A template can
have one or more types as parameters. When you use a template,
you provide the particular types or constant expressions as actual
parameters thereby creating a particular object or function.
You can create the following types of templates:
Class Templates
A class template defines a family of classes. To declare a class
template, you use the keyword template followed by the
template's formal parameters. Class templates can take parameters
that are either types or expressions. You define a template class in
terms of those parameters. For example, the following is a class
template for a simple stack class. The template has two parameters,
the type specifier T and the int parameter
size. The keyword class in the < >
brackets is required to declare any template type parameters. The
first parameter T is used for the stack element type.
The second parameter is used for the maximum size of the stack.
template<class T, int size>
class Stack
{
public:
Stack(){top=-1;}
void push(const T& item){thestack[++top]=item;}
T& pop(){return thestack[top--];}
private:
T thestack[size];
int top;
};
Class template member functions and member data use the formal
parameter type, T, and the formal parameter expression,
size. When you declare an instance of the class
Stack, you provide an actual type and a constant expression.
The object created uses that type and value in place of T
and size, respectively. For example, the following
program uses the Stack class template to create a stack
of 20 integers by providing the type int and the value
20 in the object declaration:
void main()
{ Stack<int,20> myintstack;
int i;
myintstack.push(5);
myintstack.push(56);
myintstack.push(980);
myintstack.push(1234);
i = myintstack.pop();
}
The compiler automatically substitutes the parameters you
specified, in this case int and 20, in
place of the template formal parameters. You can create other
instances of this template using other built-in types as well as
user-defined types.
Function Templates
A function template defines a family of functions. To declare a
function template, use the keyword template to define
the formal parameters, which are types, then define the function in
terms of those types. For example, the following is a function
template for a swap function. It simply swaps the values of its two
arguments:
template<class T>
void swap(T& val1, T& val2)
{
T temp=val1;
val1=val2;
val2=temp;
}
The argument types to the function template swap are
not specified. Instead, the formal parameter, T, is a
placeholder for the types. To use the function template to create an
actual function instance (a template function), you simply call the
function defined by the template and provide actual parameters. A
version of the function with those parameter types is created
(instantiated).
For example, the following main program calls the function
swap twice, passing int parameters in the first
case and float parameters in the second case. The
compiler uses the swap template to automatically create
two versions, or instances, of swap, one that takes
int parameters and one that takes float
parameters.
void main()
{ int i=2, j=9;
swap(i,j);
float f=2.2, g=9.9;
swap(f,g);
}
Other versions of swap can be created with other
types to exchange the values of the given type.
|
|
Template Instantiation
|
|
When the compiler generates a class, function or static data
members from a template, it is referred to as template instantiation.
There are three methods of invoking template instantiation:
Explicit Instantiation
You request explicit instantiation by using the explicit template
instantiation syntax (as defined in the ANSI/ISO C++ International
Standard) in your source file.
You can request explicit instantiation of a particular template
class or a particular template function.
In addition, member functions and static data members of class
templates may be explicitly instantiated.
Explicit instantiation of a class instantiates all member
functions and static data members of that class, regardless of
whether or not they are used. For example, following is a request to
explicitly instantiate the Table template class with char*:
template class Table;
When you specify an explicit instantiation, you are asking the
compiler to instantiate a template at the point of the explicit
instantiation in the translation unit in
which it occurs.
Usage
This might, for example, be useful when you are building a library
for distribution and want to create a set of compiler-generated
template specializations that you know will most commonly be used.
Then when an application is linked with this library, any of these
commonly used specializations need not be instantiated.
Another scenario might be a frequently used library that contains
a repository of template specializations for your development team.
Instantiating all such specializations in one, known translation
unit would allow easy maintenence when changes are needed and
eliminate cases of duplicate definition.
Performance:
Although time is required to analyze and design code for explicit
instantiation, compilation may be faster than for the equivalent
implicit instantiation.
Examples:
Following are examples of explicit and implicit instantiation:
- Class Template
- Function Template
Class Template
Following are examples of explicit and implicit instantiation syntax
for a class template:
template <class T> class Array; // forward declaration for the
// Array class template
template <class T> class Array {/*...*/}; // definition of the
// Array class template
template class Array <int>; // request to explicitly
// instantiate Array<int>
// template class
Array <char> tc; // use of Array<char>
// template class which
// results in implicit
// instantiation
Function Template
Following are examples of explicit and implicit instantiation syntax
for a function template:
template <class T> void sort(Array<T> &); // declaration for the sort()
// function template
template <class T> void sort(Array<T> &v) {/* ... */};
// definition of the sort()
// function template
template void sort<char> (Array <char>&); // request to explicitly
// instantiate the sort<char> ()
// template function
//NOTE <char> is not requird if the compiler can deduce this.
void foo() {
Array <int> ai;
sort(ai); // use of the sort<int> ()
} // template function which
// results in implicit instantiation
Command-Line Instantiation
By using a template option on the aCC command line, you can:
- Close a library or set of link units, to satisfy all unsatisfied instantiations without
creating duplicate instantiations.
- Specify what templates to instantiate for a given translation unit.
- Name and use template files in the same way as for the cfront based HP C++ compiler.
- Request verbose information about template processing.
Compile-Time Instantiation
By default, compile-time instantiation is in effect. Instantiation
is attempted for any use of a template in the translation unit where
the instantiation is used. All used template functions, all static
data members and member functions of instantiated template classes,
and all explicit instantiations are instantiated in the resulting
object file.
If there are duplicate instantiations at link-time, the linker
arbitrarily selects an instantiation for inclusion in the a.out or
shared library.
The following command-lines are equivalent; each compiles a.C
using compile-time instantiation.
aCC -c +inst_compiletime a.C
aCC -c a.C
Scope
If your source code contains templates and you do not specify any
template command-line options nor explicit instantiations,
compile-time instantiation takes place for any use of a template. If
you specify a template command-line option, the option takes
precedence for all translation units on the command line. Any
explicit instantiation takes precedence over either a command-line
option or compile-time instantiation.
Usage
Compared with developer-directed instantiation, compile-time
instantiation involves less coding time for the developer. However,
the design of your application may require the use of some form of
directed instantiation.
Why Use Compile-Time Instantiation
- Compile-time instantiation is the default. It is easy to
use.
- Your code may compile faster when using compile-time
instantiation.
- If your development environment uses a version control
system that is sensitive to file modifications, you may want to
use the current default, compile-time instantiation, to avoid
major code rebuilds.
|
| Scope and
Precedence |
|
Explicit instantiation provides instantiation for a particular
template class or template function. While command-line options and
the default compile-time instantiation provide instantiation at the
level of the translation unit.
If you use explicit instantiation in addition to command-line
options or default instantiation, explicit instantiation takes
precedence.
For example, using the +inst_compiletime option requests instantiation of
all used template functions and all static data members and member
functions of instantiated template classes within a translation
unit. Using explicit instantiation requests instantiation
of all members of a particular template class or a particular
template function.
|
| Template Processing |
|
In HP aC++, compile-time instantiation as the default template instantiation mechanism.
During compile-time instantiation, the compiler instantiates every template
entity it sees in a translation unit provided it has the required template definition.
Compile-time Template Processing
Following is an overview of compile-time template processing:
- The compiler places an
instantiation in every .o file in which a template is used and
its definition is known. The linker arbitrarily chooses a .o
file to satisfy an instantiation request (use). Only the chosen
instantiation appears in the a.out or .sl file. Any redundant
instantiations in other .o files are ignored.
- No instantiation information is placed in object (.o) files.
The linker is responsible for ignoring duplicate instantiations.
- No .I files are created . All
.o files are compiled only once.
|
| Migrating from Automatic Instantiation to Compile-Time Instantiation |
|
If you have used automatic instantiation with earlier versions of HP aC++
there will be some known migration problems.
The following migration problems may occur:
- creating object files
- creating an executable
- closing a set of object files prior to creating a library
(.a or .sl)
- creating a shared library (.sl)
The following sections describe specific migration scenarios and
illustrate possible migration problems and solutions:
Possible Duplicate Symbols in Shared Libraries
An existing compiler defect may be more apparent, if in HP aC++
A.02.00 or A.01.04 and prior versions you built a shared library
using automatic instantiation (the prior default using the assigner)
and now build that library using the current default (compile-time)
instantiation. The defect relates to template objects with
constructors or other runtime initializers that have been globally
defined in more than one shared library on the link line. If such an
object is defined in n shared libraries, it will be
initialized and destructed n times at runtime.
When building the same application with the current default, the
libraries are not closed prior to the final link, and the likelihood
of a template symbol being defined in more than one shared library
will increase.
Possible Duplicate Symbols in Archive Libraries
If in HP aC++ A.02.00 or A.01.04 and prior versions you built an
archive library using automatic instantiation (the prior default
using the assigner) and you rebuild that library using the current
default (compile-time) instantiation, it is possible that duplicate
symbol problems not apparent in the prior release will generate
errors in the current release.
This is because the current default uses the linker rather than
the assigner to determine which object file to pick to satisfy
instantiation requests. For example, when your archive library is
linked with an application, library objects in the link may be
different than those used when linking the library in a prior
release.
Following are two examples of building an archive library, one
built with +inst_auto/+inst_close (the prior default), the other
built with the current (compile-time) default.
Building an Archive Library with +inst_auto/+inst_close
Suppose for lib.inst_auto.a, the linker chooses foo2.o to resolve
symbol x, and foo3.o to resolve symbol stack <int>. Symbols x, y,
and stack <int> are each resolved with no duplicates.
lib.inst_auto.a
-------------------------------------------------
| foo.o | foo2.o | foo3.o |
| | | stack<int>|
| x | x | y |
| y | | |
-------------------------------------------------
Building an Archive Library with the Default (Compile-time
Instantiation)
Suppose for lib.default.a, the linker chooses foo2.o to resolve
symbol x, and foo.o to resolve symbol stack <int>. Symbols x, y, and
stack <int> are each resolved, but now there's a duplicate
definition of symbol x. This will cause a linker duplicate symbol
error. This is really a user error, but was not visible before.
NOTE: Note that this example is not meant to
account for all cases of changed behavior.
lib.default.a
--------------------------------------------------
| foo.o | foo2.o | foo3.o |
| stack<int> | stack<int> | stack<int> |
| x | x | y |
| y | | |
--------------------------------------------------
Mixing Old .o and .a Files with New Ones
What happens when you mix .o and .a files compiled with HP aC++
A.02.00 or A.01.04 and prior versions with files compiled with
subsequent versions of HP aC++? The linker gives an old symbol
precedence over a new symbol of the same name. For example:
foo1.o (old) foo2.o (new) foo3.o (new)
----------- ----------- -----------
func<int> func<int> func<int>
[old symbol] [new symbol] [new symbol]
In this case the linker chooses the func <int> from foo1.o and ignores the
other two, because the old func <int> takes precedence over any of
the new ones. Note that if there were more than one old func <int>,
the linker would give a duplicate symbol error.
A Special Case of Mixing Old .o and .a Files with New Ones
bar.a (old)
-------------------------- ------------------------------------
| foo1.o (new) | | bar1.o | bar2.o | |
|------------------------| |--------------|--------| ....... |
| func<int> [new symbol] | | func<int> | | |
-------------------------- | [old symbol] | | |
------------------------------------
Since old symbols take precedence, you would expect the linker to choose
func <int> from bar1.o. However, since bar1.o is part of an archive
library, the linker will never even try to load it unless it's
needed to resolve some other symbol in foo1.o.
So if ld loads bar1.o, then it will choose func <int> from bar1.o
However, if ld does not load bar1.o, it will choose the only func <int>
that's available, and that's the one in foo1.o.
And bar1.o might not be loaded, even though it was loaded under
the old default. This behavior may not cause a problem, however, in
some cases it may. For example, in a correctly written program in
which multiple definitions of func <int> are equivalent, there is no
problem. However, if multiple definitions of func <int> are not
equivalent, the compiler does not detect the error.
Linker Error Checking Messages
Given all of the above, the linker provides error checking to help
find out what may be happening.
- By default, ld issues generic warnings like the following
when it sees a new/old pair: aCC old.o new.o
/opt/aCC/lbin/ld:
(Warning) Linker features were used that may not be supported in
future releases. The +vallcompatwarnings option can be used to
display more details, and the ld(1) man page contains additional
information. This warning can be suppressed with the +vnocompatwarnings
option.
- If you want more information: aCC old.o new.o -Wl,+vallcompatwarnings
/opt/aCC/lbin/ld: (Warning) An automatic template instantiation
for member "func ()" in file t.o has been overridden by an
explicit definition in fi le new.o. This behavior may not be
supported in future releases.
- If you want to see duplicate symbol messages based on what
would happen if compatibility with old .a and .o files were
not supported, issue the command-line: aCC old.o new.o
-Wl,+strictctti. This generates a message like the following:
/opt/aCC/lbin/ld: Duplicate symbol "func()" in files old.o
and new.o /opt/aCC/lbin/ld: Found 1 duplicate symbol(s)
- To suppress all linker compatibility warnings, use the
command-line: aCC old.o new.o -Wl,+vnocompatwarnings
|
|