OSE - Makeit User Guide

Graham Dumpleton
Dumpleton Software Consulting Pty Limited
PO BOX 3150
Parramatta, 2124
N.S.W, Australia
email: grahamd@nms.otc.com.au

Table of Contents

Advanced Features
OSE - Makeit User GuideAdvanced Features

Advanced Features

1 Introduction

While makeit is useful in its basic form, it has a number of features which extend the support generally provided by make environments. A number of these additional features are described in this chapter.

2 Third Party Tools

A range of tools are available which can assist in debugging of memory and performance problems in applications. Makeit provides support for tools from two suppliers of such tools. The tools supported are Purify, Quantify, Purelink and Sentinel.

2.1 Purify

The support for Purify works with Purify Version 1.0 or 2.0, and comes in two forms as described below.

2.1.1 The Purify Option

To enable support in this form, `purify' must be listed in the variable MAKEIT_OPTIONS.

  MAKEIT_OPTIONS := purify
The definition of the MAKEIT_OPTIONS variable must be included in the initialisation section of the makefile.

The first form of support does not effect the standard process for generating a program executable. When you run:

  makeit one
where `one' is listed in the PROGRAMS variable, a normal program executable will be created, however, if you run:

  makeit one.pure
a variant of the program is created called `one.pure', which has been processed by Purify. As with a normal program executable, the program is placed into the makeit subdirectory. To run the Purify'd program you would need to type:

  SUN4_dbg/one.pure
In addition to being able to generate individual programs, all programs can be generated in their Purify'd form in one operation. To have makeit do this you should use the target `programs.pure'.

  makeit programs.pure
When makeit is invoked by the user to generate any program in its Purify'd form, the program will always be recompiled and linked, even if the program is up to date with respect to the programs dependencies. This approach has been taken to simplify the handling of dependencies for programs processed by Purify.

When a program needs to link with an object file, generated from a code file listed in the variable NONLIBSRC, it is necessary to provide a dependency of the form:

  $(MK)/one : $(MK)/six.o
at the end of your makefile. If Purify is being used in the form described above, it is necessary to modify the dependency to show that the Purify'd version of the program is also dependent on the object file.

  $(MK)/one $(MK)/one.pure : $(MK)/six.o

2.1.2 The Purify All Option

The second form of support for Purify is enabled by listing `purify_all' in the variable MAKEIT_OPTIONS.

  MAKEIT_OPTIONS := purify_all
When this form of support for Purify is enabled, all programs in a directory will be processed by Purify as the default. Thus invoking:

  makeit one
will result in Purify being run. The name of the program executable generated in this case will still be `one', and as before, will be placed into the makeit subdirectory.

When the `purify_all' option is being used, a program executable will only be rebuilt if they are out of date with respect to their dependencies. If a program needs to link with an object file generated from a code file listed in NONLIBSRC, it is not necessary to modify the dependency rule, which shows the program to be dependent upon that object file.

2.1.3 Customising Purify

The actions of Purify, when it processes a program executable, can be modified by setting environment variables. Instead of having to set these variables in your environment, you can set them in your makefile. When makeit runs Purify, it will export these variables into the environment for you, so that Purify will see them.

The names of the environment variables differ between version 1.0 and 2.0 of Purify. If you are using version 1.0 of Purify, you can set the following variables:

For the exact meaning of these variables you should consult your Purify manual. If you define the variables, the definitions should appear in the definitions section of your makefile. For example:

  PURIFYALWAYSUSECACHEDIR := yes
If you are using version 2.0 of Purify, customisation of Purify is performed using the single variable PURIFYOPTIONS. For example:

  PURIFYOPTIONS := -always-use-cache-dir
Again, consult your Purify documentation as to the exact options which may be listed in the PURIFYOPTIONS variable.

2.2 Quantify

Similar to Purify, support for Quantify comes in two forms. Note that use of Purify and Quantify is mutually exclusive. It is not possible to run both Purify and Quantify on the same program at the same time. If both Purify and Quantify are selected, Purify will take precedence.

2.2.1 The Quantify Option

To enable support in this form, quantify must be listed in the variable MAKEIT_OPTIONS.

  MAKEIT_OPTIONS := quantify
The definition of the MAKEIT_OPTIONS variable must be included in the initialisation section of the makefile.

The first form of support does not effect the standard process for generating a program executable. When you run:

  makeit one
where `one' is listed in the PROGRAMS variable, a normal program executable will be created, however, if you run:

  makeit one
where `one' is listed in the PROGRAMS variable, a normal program executable will be created, however, if you run:

  makeit one.pure
a variant of the program is created called `one.pure', which has been processed by Quantify. As with a normal program executable, the program is placed into the makeit subdirectory. To run the Quantify'd program you would need to type:

  SUN4_dbg/one.pure
In addition to being able to generate individual programs, all programs can be generated in their Quantify'd form in one operation. To have makeit do this you should use the target `programs.pure'.

  makeit programs.pure
When makeit is invoked by the user to generate any program in its Quantify'd form, the program will always be recompiled and linked, even if the program is up to date with respect to the programs dependencies. This approach has been taken to simplify the handling of dependencies for programs processed by Quantify.

When a program needs to link with an object file, generated from a code file listed in the variable NONLIBSRC, it is necessary to provide a dependency of the form:

  $(MK)/one : $(MK)/six.o
at the end of your makefile. If Quantify is being used in the form described above, it is necessary to modify the dependency to show that the Quantify'd version of the program is also dependent on the object file.

  $(MK)/one $(MK)/one.pure : $(MK)/six.o

2.2.2 The Quantify All Option

The second form of support for Quantify is enabled by listing `quantify_all' in the variable MAKEIT_OPTIONS.

  MAKEIT_OPTIONS := quantify_all
When this form of support for Quantify is enabled, all programs in a directory will be processed by Quantify as the default. Thus invoking:

  makeit one
will result in Quantify being run. The name of the program executable generated in this case will still be `one', and as before, will be placed into the makeit subdirectory.

When the `quantify_all' option is being used, a program executable will only be rebuilt if they are out of date with respect to their dependencies. If a program needs to link with an object file generated from a code file listed in NONLIBSRC, it is not necessary to modify the dependency rule, which shows the program to be dependent upon that object file.

2.2.3 Customising Quantify

The actions of Quantify, when it processes a program executable, can be modified by setting an environment variable. Instead of having to set this variable in your environment, you can set it in your makefile. When makeit runs Quantify, it will export this variable into the environment for you, so that Quantify will see it. The name of the variable used to customise Quantify is QUANTIFYOPTIONS. Consult your Quantify documentation as to the exact options which may be listed in the PURIFYOPTIONS variable.

2.3 Purelink

Unlike Purify and Quantify, Purelink can only be configured to run in one way. This is described below.

2.3.1 The Purelink All Option

Use of Purelink for all compilations is enabled by listing `purelink_all' in the variable MAKEIT_OPTIONS.

  MAKEIT_OPTIONS := purelink_all
Invoking:

  makeit one
will thus result in Purelink being run. The name of the program executable generated in this case will still be `one', and usual will be placed into the makeit subdirectory.

When the `purelink_all' option is being used, a program executable will only be rebuilt if they are out of date with respect to their dependencies.

2.3.2 Customising Purelink

The actions of Purelink, when it processes a program executable, can be modified by setting an environment variable. Instead of having to set this variables in your environment, you can set it in your makefile. When makeit runs Purelink, it will export this variable into the environment for you, so that Purelink will see it.

Customisation of Purelink is performed using the single variable PURIFYOPTIONS. Consult your Purelink documentation as to the exact options which may be listed in the PURELINKOPTIONS variable.

2.4 Sentinel

The support for Sentinel works similarly to that of Purify and Quantify. The two methods of use are described below.

2.4.1 The Sentinel Option

To enable support in this form, `sentinel' must be listed in the variable MAKEIT_OPTIONS.

  MAKEIT_OPTIONS := sentinel
The definition of the MAKEIT_OPTIONS variable must be included in the initialisation section of the makefile.

The first form of support does not effect the standard process for generating a program executable. When you run:

  makeit one
where `one' is listed in the PROGRAMS variable, a normal program executable will be created, however, if you run:

  makeit one.sent
a variant of the program is created called `one.sent', which has been processed by Sentinel. As with a normal program executable, the program is placed into the makeit subdirectory. To run the Sentinel'd program you would need to type:

  SUN4_dbg/one.sent
In addition to being able to generate individual programs, all programs can be generated in their Sentinel'd form in one operation. To have makeit do this you should use the target `programs.sent'.

  makeit programs.sent
When makeit is invoked by the user to generate any program in its Sentinel'd form, the program will always be recompiled and linked, even if the program is up to date with respect to the programs dependencies. This approach has been taken to simplify the handling of dependencies for programs processed by Sentinel.

When a program needs to link with an object file, generated from a code file listed in the variable NONLIBSRC, it is necessary to provide a dependency of the form:

  $(MK)/one : $(MK)/six.o
at the end of your makefile. If Sentinel is being used in the form described above, it is necessary to modify the dependency to show that the Sentinel'd version of the program is also dependent on the object file.

  $(MK)/one $(MK)/one.sent : $(MK)/six.o

2.4.2 The Sentinel All Option

The second form of support for Sentinel is enabled by listing `sentinel_all' in the variable MAKEIT_OPTIONS.

  MAKEIT_OPTIONS := sentinel_all
When this form of support for Sentinel is enabled, all programs in a directory will be processed by Sentinel as the default. Thus invoking:

  makeit one
will result in Sentinel being run. The name of the program executable generated in this case will still be `one', and as before, will be placed into the makeit subdirectory.

When the `sentinel_all' option is being used, a program executable will only be rebuilt if they are out of date with respect to their dependencies. If a program needs to link with an object file generated from a code file listed in NONLIBSRC, it is not necessary to modify the dependency rule, which shows the program to be dependent upon that object file.

2.4.3 Customising Sentinel

Sentinel is customised through a configuration file called `.sentinel' either in the directory where Sentinel is run, or in your home directory. Consult your documentation for Sentinel, for further information.

3 Templates

Makeit provides general, as well as compiler specific support for C++ compilers supporting templates. In addition, for those C++ compilers which do not support templates, a template preprocessor is provided to allow the template feature of C++ to be used. In the majority of cases, major differences between the way in which C++ compilers handle template instantiation is hidden from the user. As long as a few simple organisational rules are followed, this allows C++ code to be moved easily from one C++ compiler to another.

3.1 Organisation of Template Files

The recommended organisation for your template files, if you create your own template classes, is always to place the class template declaration in a separate file from the member function template declarations. For example, if you create the template class vector, place the class template declaration in a header file called `vector.hh'. In the same directory, you should create a file `vector.c' and in it, place the member function templates for the class vector. In the following discussion, these two files will be referred to as the template header file and template code file.

If you want your code to be portable to any C++ compiler supporting templates, the extension you use on the template files is important. The first letter in the extension of the template header file should be `h'. The extension of the template code file should be the single character `c'. Whether you use an upper or lower case character for the template header file is not important. Whichever case you use for the extension of the template header file, you should use the same case for the extension of the template code file.

Both of the template files are in essence, header files, as they will be included into other files either by you or the C++ compiler. As the template code file will have an extension of either `c' or `C' though, the danger exists, that makeit will attempt to compile the file directly. To prevent this, you should list the names of the template code files in the EXCLUDE variable. For example:

  EXCLUDE := vector.c
If you are writing a number of template classes, a better solution would be to place all header files, standard header files, and template files, into a separate directory. If this is done, you will need to remember to pass the location of that directory onto the C preprocessor using the CPPFLAGS variable. For example, if you placed all header files in the adjacent directory `include', you would need to add the following to your makefile.

  CPPFLAGS := -I../include
Due to template code files being treated as header files, it is important to guard against multiple inclusion of the file. The standard technique of using `#ifndef' guards around a file is used to prevent multiple inclusion. For example:

  #ifndef VECTOR_C
#define VECTOR_C

// Place member function templates here.

#endif /* VECTOR_C */
The final requirement of the organisational structure being recommended is that the template code file be included, using the C preprocessor `#include' directive, at the end of the template header file. Inclusion of the template code file into the template header file should only be carried out when the C preprocessor symbol EXPAND_TEMPLATES is defined. Your template header file would thus look something like this:

  #ifndef VECTOR_HH
#define VECTOR_HH

template<class T>
class vector
{
...
};

#if defined(EXPAND_TEMPLATES)
#include "vector.c"
#endif

#endif /* VECTOR_HH */
When makeit is run, it will define the symbol EXPAND_TEMPLATES when compiling C++ program code files. This will result in both the class templates and member function templates being seen by the C++ compiler, when compiling the program code file. The C++ compiler is then able to expand any templates required, directly into the program object file. In order for this to be successful, the program code file must include the header files of any classes being used by the program which make use of template classes.

In some situations a template class may be used in the implementation of a member function of a class. The problem is though, that there is no evidence in the header file for that class, that the template class is being used. So that the C++ compiler can detect that the template class is being used, the template header file for the template class must be included into the header file of the class which uses it, and a dummy typedef added to the class header file. This is illustrated below.

  #include "vector.hh"

// vector<int> used in implementation, include dummy
// typedef here to force expansion of template into
// program object file

typedef vector<int> myclass_typedef1;

class MyClass
{
...
};
Expansion of templates directly into a program object file typically results in the shortest compilation times. Other methods for expanding templates are supported by the various C++ compilers supporting templates. If you organise your code as described above, and do not rely on features particular to a C++ compiler, you should be able to move your code more easily from one compiler to another.

3.2 GNU C++ Templates

If you are using GNU C++, it is necessary to include pragmas in your template header fies. The pragmas ensure that template implementations will be expanded correctly. Unlike ordinary code files, you should not put a pragma in the template code file. Both the `interface' and `implementation' pragmas should be placed in the template header file. The `implementation' pragma though, should only be seen by the compiler when expanding templates. Using makeit, the preprocessor symbol EXPAND_TEMPLATES is defined when templates are being expanded. Therefore, a check can be made for this symbol, and the `implementation' pragma only included when the symbol is defined. When included, the `implementation' pragma must appear before the `interface' pragma. You should always list the name of the template header file as argument to the `implementation' pragma. The name should be listed as it would be included by the user. For example, if the header file is include from a subdirectory, the name of the subdirectory should be included.

Adding the pragmas to the above example, you would get:

  #ifndef VECTOR_HH
#define VECTOR_HH

#ifdef __GNUG__
#if defined(EXPAND_TEMPLATES)
#pragma implementation "vector.hh"
#endif
#if (__GNUC__ >= 3 || __GNUC_MINOR__ >= 6)
#pragma interface "vector.hh"
#else
#pragma interface
#endif
#endif

template<class T>
class vector
{
...
};

#if defined(EXPAND_TEMPLATES)
#include "vector.c"
#endif

#endif /* VECTOR_HH */

3.3 Template Preprocessor

If you are using a compiler which does not support templates, it is still possible to make use of templates in your code by using the template preprocessor supplied with OSE. Using the template preprocessor though, does require you to add information into your code files in order to trigger the expansion of the templates. A special header file which initialises the template preprocessor, must also be included at the start of any template header files which you create.

The template preprocessor supplied with OSE is a modified version of the COOL template preprocessor. The major changes which have been made to the COOL preprocessor include modification to support the correct template syntax for class and member function templates, and the addition of support for static class member variable initialisers. The template instantiation mechanism has also been extended and improved.

Note though, that the template preprocessor, does not support the complete template syntax as described in the Annotated \cxx{C++} Reference Manual. In particular, the template preprocessor does not support function templates. Other subtle aspects of templates are probably also not supported.

3.3.1 Initialising the Preprocessor

The header file, which you need to include at the start of your template header files, will vary depending on whether you are using the OSE C++ class libraries. If you are using the OSE C++ class libraries you would have `ose' listed in the MODULES variable. You would then include the file `OTC/OTC.h' at the start of your template header file. Your template header file would look something like:

  #ifndef VECTOR_HH
#define VECTOR_HH

#include <OTC/OTC.h>

template<class T>
class vector
{
...
};

#if defined(EXPAND_TEMPLATES)
#include "vector.c"
#endif

#endif /* VECTOR_HH */
If you are not using the OSE C++ class libraries, firstly you will have to let the C preprocessor know where to search for the OSE C++ library header files. To achieve this, add the following to your makefile.

  CPPFLAGS := -I$(OSE_HOME)/include
When the OSE C++ class libraries are not being used, you will not be able to use the include file `OTC/OTC.h' to initialise the template preprocessor. This is because that file includes definitions which will require your programs to link with the OSE C++ class library. If the header file were included and you are not linking with the library, the compiler will fail to link your program due to undefined symbols. In this case, you should include the file `OTC/ansi/template.hh'. This file only contains code to initialise the template preprocessor and will not result in the OSE C++ class library having to be linked with your program.

3.3.2 Expanding the Templates

At the point that you use a template class, you need to force the template preprocessor to expand the template first. If the template class isn't expanded, then the C++ compiler will signal that you are using an undefined type. As an example, if you wished to use vector<int> in your code you need to include the following:

  #include "vector.hh"

#ifdef __OSE_TEMPLATES__
OSE_TEMPLATE vector<int>;
#endif
This must be at file scope and appear before the point that you first use vector<int> in that file. The template header file for the class template must also be included into your file. The template header file must be included before the OSE_TEMPLATE declaration.

If you wish to be able to compile your code using a C++ compiler supporting templates, you must surround the OSE_TEMPLATE declaration with the check for the preprocessor symbol `__OSE_TEMPLATES__'. The symbol `__OSE_TEMPLATES__' is defined explicitly by the template preprocessor, the check allows the OSE_TEMPLATE declaration to be excluded, when using a true template compiler.

The semicolon at the end of the OSE_TEMPLATE declaration is not mandatory, however it is recommended that it be included. The reason for this is that C++ code browsers that use fuzzy parsing techniques will trip up on the OSE_TEMPLATE declaration. By including the semicolon, those C++ code browsers are able to recover and continue parsing the remainder of the file. C++ code browsers, for which this has been found to be necessary, include sniff and xcoral.

In order to use the template preprocessor correctly, it is important to understand how the instantiation of templates actually works. To illustrate more clearly what is occurring, you can rewrite the above example as:

  #include "vector.hh"

#ifdef __OSE_TEMPLATES__
OSE_DECLARE vector<int>;
OSE_IMPLEMENT vector<int>;
#endif
The inclusion of the OSE_TEMPLATE declaration results in the expansion of both the class template declaration, via OSE_DECLARE, and the member function implementation for the template listed, via OSE_IMPLEMENT. If the template class has been expanded for that set of template arguments previously, it will not be expanded again. Although OSE_IMPLEMENT will always be encountered by the template preprocessor, it does not result in the template member functions being expanded into every object file that includes the OSE_TEMPLATE declaration.

The reason that the template member functions are not expanded into every object file is that the template preprocessor can only expand the template member functions, if the code implementing them has been seen by it. If you have followed the structure for organising your template header files as described above, the template member functions will only be seen by the template preprocessor when the symbol EXPAND_TEMPLATES is defined. When you run makeit, it generally only defines the symbol EXPAND_TEMPLATES when compiling a program code file. This means that the template member functions will only be expanded into the program object file.

Since the implementation of any templates being used are only expanded into the program object file, the program code file must include any header files that contain an OSE_TEMPLATE declaration. If you use a class template in a code file then you must include an OSE_TEMPLATE declaration in a header file which is seen by the template preprocessor when compiling the program. This is similar to the requirement of providing a typedef in the header file for true template compilers. Specifically you would need to have:

  #include "vector.hh"

// vector<int> used in implementation, include dummy
// typedef here to force expansion of template into
// program object file

#ifdef __OSE_TEMPLATES__
OSE_TEMPLATE vector<int>
#endif

typedef vector<int> myclass_typedef1;

class MyClass
{
...
};

3.3.3 Forward Declarations and Templates

Since it is necessary for you to forcibly expand a template using OSE_TEMPLATE before it is used, it is sometimes necessary to forward declare classes, which are being used as arguments to the template. For example, consider the case below, in which a class uses a template where the argument to the template is a pointer to an object of that class.

  class MyClass
{
private:
vector<MyClass*> myVec;
};
If a OSE_TEMPLATE declaration for vector<MyClass*> were added just before the class, the C++ compiler would complain about MyClass being an unknown type. The situation thus requires that a forward declaration of MyClass be provided, as shown below.

  #ifdef __OSE_TEMPLATES__
class MyClass;
OSE_TEMPLATE vector<MyClass*>;
#endif

class MyClass
{
private:
vector<MyClass*> myVec;
};
The above, will not work if any member functions of the template class vector attempt to access a data member or call a member function of the type MyClass. This is because the implementation of the member functions for vector will be expanded into the code at the point of the OSE_TEMPLATE declaration, which is before class MyClass has been fully defined. In this situation, you will need to take explicit control of the expansion of the declaration and implementation of a template class. To do this, you will need to use the OSE_DECLARE and OSE_IMPLEMENT macros directly. The above example can be rewritten as:

  #ifdef __OSE_TEMPLATES__
class MyClass;
OSE_DECLARE vector<MyClass*>;
#endif

class MyClass
{
private:
vector<MyClass*> myVec;
};

#ifdef __OSE_TEMPLATES__
OSE_IMPLEMENT vector<MyClass*>;
#endif
If the member function which was trying to access or use MyClass was an inline function, this will still fail. To avoid failure, any inline member functions of the template class, which try to access or use the class must be converted into normal member functions and not be made inline.

3.3.4 Overriding a Template Class

The standard for templates in C++ allows you to provide user override templates. These are classes which override a class template for a specific set of arguments and are used by the compiler instead of the default implementation of the template. In the example below, vector<MyClass*> is a user override template class.

  template<class T>
class vector
{
...
};

class MyClass
{
...
};

class vector<MyClass*>
{
...
};
User override template classes allow you to provide an alternative implementation of the template class which is tailored to the type of the template argument. If you provide a user override template class you should place it in the same header file as one of the types given as argument to the template. If the override template class is not placed in the same header file, some template compilers will not find it and will still use the default template version, not your version.

If you are using the template preprocessor, you must tell the preprocessor that you have overridden the template class for a specific set of arguments. If you do not, when OSE_TEMPLATE is used for that template class, with that set of arguments, it will still expand the original template. This will result in the C++ compiler complaining that two versions of the class exist.

To let the template preprocessor know that you have overridden a template, you must use the macro OSE_MARK_TEMPLATE. A declaration using this macro must be included in the same file as your version of the template. For example:

  template<class T>
class vector
{
...
};

class MyClass
{
...
};

#ifdef __OSE_TEMPLATES__
OSE_MARK_TEMPLATE vector<MyClass*>;
#endif

class vector<MyClass*>
{
...
};

3.3.5 Templates and Inheritance

If you create a class template which is derived from another class template, you need to ensure that the base class template will be expanded before the derived class template. This could be achieved by expecting the user to use OSE_TEMPLATE for each class in the correct order, however this would be error prone, as it would expect the user to know the complete class hierarchy. Instead, you can supply a OSE_DECLARE and OSE_IMPLEMENT declaration with your template class, which will result in the automatic expansion of the base class when the derived class is expanded. The declarations can only be expanded at this point, as the arguments to the base class template are usually the same as that for the derived class template.

To delay the expansion of the base class template till the time that the derived class template is expanded, a special syntax, only understood by the template preprocessor, must be used. This special syntax is given below.

  template<class T> TemplateClassName
{
...
};
In this statement `TemplateClassName' should be replaced with the name of the derived template class. The template argument list should match the argument list for the derived template class. You should then add an OSE_DECLARE statement in the body of the statement to expand the base class. Note that since this syntax is only understood by the template preprocessor, you should enclose it in an `#ifndef'. For example:

  template<class T>
class MyTemplate1
{
...
};

#ifdef __OSE_TEMPLATES__
template<class T> MyTemplate2
{
OSE_DECLARE MyTemplate1<T>;
};
#endif

template<class T>
class MyTemplate2 : public MyTemplate1<T>
{
...
};
It is important that the OSE_DECLARE be included in one of these statements, just before the declaration of the derived class template. The statement being before the template class declaration, will ensure that the base class template will be expanded before the derived class template.

To expand the implementation of the base class template, a similar statement is included at the beginning of the template code file. In this case, you should use an OSE_IMPLEMENT declaration.

  #ifdef __OSE_TEMPLATES__
template<class T> MyTemplate2
{
OSE_IMPLEMENT MyTemplate1<T>;
};
#endif
As this will be seen by the template preprocessor after it has seen the declaration for the derived class template, it will only be expanded when the implementation of the derived class template is expanded.

It is important that you use OSE_DECLARE and OSE_IMPLEMENT in two separate statements, instead of just OSE_TEMPLATE in one statement, before the template class declaration. If you did just use OSE_TEMPLATE and you needed to take control of when the declaration and implementation were expanded, (as was described in the previous section), the compiler could generate errors. This will be because the implementation of the base class template will be expanded, when you asked for the derived class template declaration to be expanded. If the base class template member functions tried to use some feature of the class being used as argument to the template, it would fail as that class would not yet have been defined.

3.3.6 Templates Using Templates

Another situation, where you need to ensure template classes are expanded automatically, is where a template class uses another template class, either in its interface, or implementation. This situation is handled in a similar manner to that for derived classes. For example:

  template<class T>
class MyTemplate1
{
...
};

#ifdef __OSE_TEMPLATES__
template<class T> MyTemplate2
{
OSE_DECLARE MyTemplate1<int>;
};
#endif

template<class T>
class MyTemplate2
{
MyTemplate1<int> myData;
};
Again, it is best to have the OSE_IMPLEMENT declaration separate.

3.4 Template Instantiation File

If you are using the template preprocessor, it is possible to expand any templates required into an object file that is separate to the program object file. The separate object file would then be linked with the program object file and library to create the program executable. If you have a number of programs in a directory, this can save on compilation time as the template preprocessor will only need to expand the templates once.

The file into which all the templates will be expanded is a template instantiation file. In addition to the template preprocessor supporting this feature, some true C++ template compilers also support this feature. These compilers include the ObjectStore C++ compiler, DEC C++ compiler and IBM XL C++ compiler.

To use this feature you should create a template instantiation file and add a definition to your makefile which lists the name of the file in the TEMPLATES variable. For example, if you named your file `templates.cc' you would need to include the following in your makefile.

  TEMPLATES := templates.cc
In this file, you should now include the header files of all the classes which are used in your program. Instead of defining the preprocessor symbol EXPAND_TEMPLATES when compiling your program code files, makeit will define it only when compiling the template instantiation file. For C++ compilers such as DEC C++, makeit also defines any compiler options necessary to force expansion of templates. In the case of DEC C++ this is the `-define_templates' option. If you are using a compiler which does not support template instantiation files, the fact that you have defined the TEMPLATES variable will be ignored.

3.5 Cfront C++ Template Instantiation

When using a cfront based C++ compiler, instantiation of templates in general will be directly into the program object file. In circumstances where the program code file does not include all header files for classes used in a program, or a typedef has not been provided to force the expansion of a template, as previously described, cfront compilers will resort to compiling individual template classes and placing them into a template repository.

When using makeit the name of this repository is defined by the variable MKPTR. By default the MKPTR variable is defined to be `$(MK).ptr', where `$(MK)' is the name used for the standard makeit subdirectory. If the C++ compiler has to expand template classes individually into the repository, compilation times will typically be longer. It is therefore encouraged that you include all header files for classes used by a program into the program code file. If you also include the special macros required by the template preprocessor it will allow you to compile your code using either the template preprocessor or a true C++ template compiler.

When using a cfront based C++ compiler, if you want the compiler to only use the template repository and not instantiate templates into program code files, the `use_repository_only' option should be listed in the MAKEIT_OPTIONS variable. This option is equivalent to supplying the `-ptn' to the compiler.

When expanding templates into the template repository, cfront based C++ compilers must know which header file, and for templates, which code file to include. Normally the compiler will collect this information while compiling your code files and put that information into the repository directory so that it can use it when expanding the templates. If however, you are using a C++ library that internally uses class templates, and you do not include any header files which show they are being used, where the templates are found, or where the classes used as arguments to the templates are found, the compiler will not know where to find the header files and template code files.

When the compiler doesn't know which files to use, it will use a default strategy to find those files. The strategy used is to expect that the header and code files have the same basename as the name of the class. In addition, the compiler expects that the files are not in a subdirectory of those directories searched for include files. For example, if a class EX_Example is used as an argument to a template class, it will try to include the file `EX_Example.h'. If the name of the header file is not the same as the class name, you have used a different extension, or the file is in a subdirectory, the compiler will fail.

To avoid this, a library provider can provide a map file, which indicates those files that contain classes. For example, such a file may include the following for the EX_Example class.

  @dec EX_Example
<mylib/example.hh>
If a library provider has made a map file available, makeit can direct cfront based C++ compilers to consult that file when expanding templates into the repository. For this to be done, you should add a definition to your makefile which lists the location of any map files into the PTMAPS variable. For example:

  PTMAPS := ../mylib/PTMAP
If a library provider has not created a map file, you could always create one yourself, placing it in your directory and setting PTMAPS appropriately.

4 Shared Libraries

Makeit provides the ability to create shared libraries on platforms which support that type of library. This feature is enabled by listing `shlib' in the MAKEIT_OPTIONS variable. The MAKEIT_OPTIONS variable must be set in the initialisation section of your makefile.

  MODULES := c cc
MAKEIT_OPTIONS := shlib
The process involved in creating a shared library is not that different to a static library. To create a shared library, makeit will compile each library code file, directing the compiler to produce a PIC object, and then link the PIC objects together to form the shared library.

Creation of the shared library is initiated by invoking makeit with `shlib' as target.

  makeit shlib
Individual library code files can also be compiled. To compile the library code file `one.c' into a PIC object, the target `one.so' is used. For example:

  makeit one.so
Multiple shared library PIC objects may be listed as targets for a single invocation of makeit. Creation of the individual PIC objects will not result in the shared library being generated. The shared library is only created when the target `shlib' is used. The target `shlib' is automatically built when the target `all' is built. Thus, invoking

  makeit all
will also create the shared library.

The shared library produced by makeit is called `lib.so' and is placed in the makeit subdirectory. Makeit does not provide support for applications in the same directory linking with the shared library. Shared libraries can only be used after they have been installed in a common area.

4.1 Static Data

Implementations of shared libraries on some platforms, for example SunOS, do not allow initialised static data in the library. Any static data which is in the shared library will be set to zero. Such implementations require that code files containing initialised static data, also be compiled as normal objects. The normal versions of the object files are archived into a static library. The static library is linked into an application when linking with the corresponding shared library.

Using makeit, any library code file which contains initialised static data must have its full name listed in the STATIC variable. For example, if the file `one.c' contained initialised static data, you would need to include the following in the definitions section of your makefile.

  STATIC := one.c
This will force makeit to deal with this file as described above. The name of the static portion of the shared library which makeit creates, is called `lib.sa'. The library `lib.sa' will be placed into the makeit subdirectory after being created.

If you need to enable different code if a file is compiled as a PIC object, the preprocessor symbol `PIC' can be checked. The symbol is only defined when compiling a file into a PIC object.

WARNING: Implementations of shared libraries differ between platforms. For this reason the manual does not go into more detail on this topic. Instead, consult the documentation for your system, on how to deal with initialised static data. If code must be portable, initialised static data should be avoided.

4.2 C++ and Shared Libraries

Static instances of C++ objects in shared libraries are also a problem. The problem is that constructors for static objects in shared libraries do not get called. Static objects are therefore not initialised.

Two techniques exist to ensure that static objects are initialised. The first technique requires the static objects only to be accessed through a global function or static member function. Instead of having a static object, a static pointer to the type of the object is declared. The pointer will be initialised to zero at program startup. The first time the function is called to access the object, an instance of the object is created. The address of this object is assigned to the static pointer.

  static Object* theObject = 0;

Object& object() {
if (theObject == 0)
theObject = new Object;
return *theObject;
}
Using this technique, the object is only created if an attempt is made to access the object. If the object is never accessed it is never created. If the object is created, it is never destroyed.

The second technique uses the nifty counter method of initialisation. In this approach, a helper class is created, which keeps a count of how many instances of the helper class exist. The first time an instance of the helper class is created, the static object is initialised. When the last instance of the helper class is destroyed, the static object is destroyed.

An implementation of the helper class is:

  class ObjectInit
{
public:

ObjectInit()
{
if (count++ == 0)
theObject = new Object;
}

~ObjectInit()
{
if (--count == 0)
{
delete theObject;
theObject = 0;
}
}
private:

static int count;
};

int ObjectInit::count = 0;
A definition for a static instance of ObjectInit class should be added into a header file. The header file containing the definition must be included in any code using the static instance of the Object. At least one of the code files including the header file must be compiled as a static object and not as a PIC object.

The form of the definition in the header file should be:

  static ObjectInit _ObjectInit;
If it is not possible to change your code, such that a pointer to a global object is used, a variation of the ObjectInit class can be used. The modification entails using the placement syntax for operator new() to initialise the static object. An explicit call to the destructor is used to destroy the object.

As initialisation of the static object will correctly occur in a static library, the call to operator new() should only be carried out when the code is compiled to a PIC object.

  static Object theObject;

ObjectInit::ObjectInit()
{
#if defined(PIC)
if (count++ == 0)
new (&theObject) Object;
#endif
}

ObjectInit::~ObjectInit()
{
#if defined(PIC)
if (--count == 0)
theObject.~Object();
#endif
}
Using this approach, the constructor and destructor for ObjectInit must not be inline.

The second technique described, has the benefit that the object will always be created. Creation of the object will even occur if the object isn't accessed. In addition, the destructor for the object is always called.

5 ObjectStore

Makeit provides support for developing applications which use the ObjectStore OODBMS. Using the support provided by makeit it is possible to create applications, which use ObjectStore through either the ObjectStore DML interface, or through the C++ library interface.

Support for the ObjectStore database is automatically enabled if you are using the ObjectStore C++ compiler. For instance, if you have the variable C++COMPILER defined to be:

  C++COMPILER := OS2.0
If the option `+OSTD' is not listed in the variable C++FLAGS, makeit assumes that you are using the ObjectStore DML extensions. If `+OSTD' is listed in the variable C++FLAGS, makeit assumes that you are using the C++ library interface to access the ObjectStore database.

Support for ObjectStore will also be enabled if you are using a C++ compiler other than the ObjectStore compiler if you add `OSTORE-' before the name of the C++ compiler. For example, instead of listing your C++ compiler as:

  C++COMPILER := SUN3.0.1
you would list it as:

  C++COMPILER := OSTORE-SUN3.0.1
Only the C++ library interface is available, when using a C++ compiler other than that provided with ObjectStore. To identify that you are using a C++ compiler other than the ObjectStore C++ compiler with ObjectStore, makeit will define the preprocessor symbol ENV_OSTORE. Makeit will also define this symbol, if you are using the ObjectStore C++ compiler with `+OSTD' as an option to the compiler, ie., with the C++ library interface. As the ObjectStore C++ compiler defines the symbol `__OS_DML' when the DML extensions are being used, you can write your code to cope with all cases. For example:

  template<class T>
OTC_Vector<T>::OTC_Vector(u_int theSize)
: mySize(theSize)
{
#if defined(CXX_OS) && defined(__OS_DML)
myVector = new (os_segment::of(this)) T[theSize];
#else
#if defined(ENV_OSTORE)
myVector = new (os_segment::of(this),
OTC_TypeSpec<T>::typespec(),theSize) T[theSize];
#else
myVector = new T[theSize];
#endif
#endif
OTCLIB_ASSERT(myVector != 0);
}
The OTC_TypeSpec class in the above example is a special template class provided with the OSE C++ class libraries. This template class may be used to obtain a typespec for a builtin type, object, or pointer to a builtin type or object. If the template argument type is not a builtin type then you must mark the type in the schema file.

5.1 Schema Databases

ObjectStore uses two schema databases to record information about objects being used in an ObjectStore database. When compiling code, it is necessary to define a location for the schema databases. The location of the databases must be within a partition in which ObjectStore can create databases.

The two databases are the compilation and application schema databases. To define the locations of the databases you should define the following variables appropriately in the definitions section of your makefile.

For further information about each database, consult your ObjectStore documentation.

5.2 Schema File

If the C++ library interface is being used with ObjectStore instead of the DML interface, you will need to generate a schema file. This schema file should mark all those types which are being used in the database. The name of the schema file must be listed in the SCHEMAS variable. The definition of the SCHEMAS variable must be included in the definitions section of your makefile. For the actual details of what you should place in the schema file, consult your ObjectStore documentation.

6 Versant

Makeit provides support for Versant compiler front ends. To enable this support, the string `VERSANT-' is prefixed to the tag you are using for your C++ compiler. For example, if you are using Sun C++ 3.0.1, you would already have:

  C++COMPILER := SUN3.0.1
To enable the use of the Versant compiler front end for this compiler, change this to:

  C++COMPILER := VERSANT-SUN3.0.1
When the Versant compiler front ends are being used, makeit forces the symbol `ENV_VERSANT' to be defined by the preprocessor. This allows different code to be used in applications or libraries, depending on whether Versant is being used.

By default, the version of the `-lcxxcls' library linked with programs built by makeit, will be the version containing debugger information. To link with the optimised version of this library, the option `production_library' should be listed in the VERSANT_OPTIONS variable.

  VERSANT_OPTIONS := production_library
In addition, a single process client will be created by default. if a dual process client is required, the option `dual_processes' should be listed in the VERSANT_OPTIONS variable.

  VERSANT_OPTIONS := dual_processes
In either case, the VERSANT_OPTIONS definition must appear in the initialisation section of your makefile.

7 ODE Persistance Store

Makeit also provides support for the compiler front end supplied with ODE from AT&T. To select use of the ODE front end, you should list `ODE-' as a prefix to the name of the C++ compiler you have selected. For example, if you are using Sun C++ 2.1, you would define the C++COMPILER variable as follows in the initialisation section of your makefile.

  C++COMPILER := ODE-SUN2.1
When the ODE front end is being used, the preprocessor symbol `ENV_ODE' is defined by makeit. You should however check for the preprocessor symbol `__oplusplus' if you need to compile different code into your application. The `__oplusplus' symbol is defined by the ODE front end.

8 ObjectCenter

Integration with ObjectCenter is provided with the `ocenter' module. The module is included by listing `ocenter' in the MODULES variable.

  MODULES := cc c ose ocenter
Before ObjectCenter can be used, it must be initialised. Initialisation is performed by running the following commands from within ObjectCenter, once it has started.

  setopt make_prog makeit
make ocenter_init
It may be necessary to include the full pathname to the makeit program when setting the `make_prog' variable. Instead of setting `make_prog' each time you run ObjectCenter, you could add the command to the `.ocenterinit' file in your home directory.

These commands have the effect of loading information about the location of header files, libraries, compiler flags and preprocessor definitions, stored in the makefile for makeit, into ObjectCenter. This avoids duplicating the information. The initialisation also initialises ObjectCenter to use makeit to build object files which are not present when a request is made to load them. Once initialised, ObjectCenter commands such as `load' and `swap' may be used as normal.

Note that when the ObjectCenter `link' command is used, it will expand any missing templates into the repository directory. To ensure that the repository directory is already populated with the required template expansions, the option `use_repository_only' should be listed in the MAKEIT_OPTIONS variable. This will cause makeit to also use the repository for all template expansions when building programs.