6. Miscellaneous

6.1 Templates

Templates are in one respect very similar to an inline function. No code is generated when the compiler sees the declaration of a template; code is generated only when a template instantiation is needed.
A function template instantiation is needed when the template is called or its address is referenced and a class template instantiation is needed when an object of the template instantiation class is declared.

The template specifier for a template class should be placed alone on the line preceding the "class" keyword or the return type for a function.  Template parameters should be in upper case.

// template declaration     template<class T>     class ListTemplate     {     public:         T front();         ...     };     // template definition     template<class T>     T ListTemplate<T>::front()     {         ...;     }

A big problem is that there is no standard for how code that uses templates is compiled. The compilers that require the complete template definition to be visible usually instantiate the template whenever it is needed and then use a flexible linker to remove redundant instantiations of the template member function. However, this solution is not possible on all systems; the linkers on most UNIX-systems cannot do this.
Even though there is a standard for how templates themselves should be compiled, there are still many compilers that do not follow the standard. Some compilers require the complete template definition to be visible, even though that is not required by the standard.
This means that we have a potential portability problem when writing code that uses templates.
=>We recommend that you put the implementation of template functions in a separate file, a template definition file, and use conditional compilation to control whether that file is included by the header file. A macro is either set or not depending on what compiler you use. An inconvenience is that you now have to manage more files. Only inclusion of the header file should be needed.

#ifndef QueueTemplate_hpp    #define QueueTemplate_hpp 
template <class T>    class QueueTemplate    {       public:          QueueTemplate();          // ...          void insert(const T &t);    }; 
 
//---------------------------------    // Template Definition    //---------------------------------    #ifndef EXTERNAL_TEMPLATE_DEFINITION    #include "QueueTemplate.cpp"    #endif 
 #endif

6.2 Program Files 

Files should all begin with a file prolog (see below).Organize a program into two types of files as follows:

  • Header File (.hpp) - should contain:
    • A class declaration
    • Any global type declarations
    • Any exceptions
    • Any typedefs
    • Any includes for template files
    • ENUM type definitions
  • Source File (.cpp) - should contain:
    • Any Static and/or Constant data values
    • Method definitions (implementation)

Organize header files by class (one class declaration per header file) or by logical grouping of functions (e.g. RealUtilities) 

  • The main procedure should reside in its own file.
  • For source files which contain related functions (utilities, for example), follow guidelines for putting functions in some meaningful order.
  • Do not use rows of asterisks to separate functions.
  • There should be only one class or namespace per .hpp/.cpp pair. There may be exceptions to this, though, if a set of namespaces or classes are small and may be logically grouped together.
6.3 Portability
  • Use ANSI/ISO C++ whenever it is available.
  • When optimizing, some thought must be given to portability issues.
  • Consider optimizations right from the start, as it is much harder to go back and redesign or recode later.
  • Pass "large" arguments (instances of classes or structs) to a function by const reference when the arguments don't need to be modified and pass as reference when they need to be modified.
  • Place typedefs for all common types (e.g. Real defined as double, Integer defined as int) in a central header file, accessible by all code, for easier portability to other platforms and to higher precision types.
6.4 Efficiency
  • For efficiency, minimize the number of constructor/destructor calls: this means minimize the number of local objects that are constructed; construct on returning from a method, rather than creating a local object, assigning to it, and then returning it; pass large objects by const reference; etc.
  • For efficiency, use exceptions only for truly exceptional conditions, not for message passing.
  • Use embedded assignments when they are proven to be more efficient than not using them.

while ((c = getchar()) != EOF) { ... }

6.5 Extern statements / External variables
  • Avoid using extern statements in the header file. Whenever possible, the source files referencing the global data should "extern" the needed global data, so that reader will know which variables are declared external to that source file.
  • Avoid declaring non-static external variables. Variables needed by more than one file should appear in a .cpp file and be externed in a source file.
6.6 Preprocessor Directives
  • Include a preprocessor directive in the main header file, accessed by all code, e.g.

#ifndef GMAT_API #define GMAT_API #endif

6.7 Mixing C and C++
  • Include files which contain code that is accepted by both C and C++ compilers should have the file name extension ".h".
  • Make the header files work correctly when they are included by both C and C++ files. If you start including an existing C header in new C++ files, fix the C header file to support C++ (as well as C), don't just extern "C" { } the C header file.

In C header file:

#ifdef __cplusplus extern "C" { #endif int existingCfunction1(…); int existingCfunction2(…); #ifdef __cplusplus } #endif

6.8 SVN Keywords
  • If SVN is used for configuration management, the top of every file should contain the following lines (or those agreed upon by the project/team):

//$Id$

  • The command to replace the keyword with actual data is:

svn propset svn:keywords Id filename

6.9 README file
  • A README file should be used to explain what the program does and how it is organized and to document issues for the program as a whole. For example, a README file might include:
    • All conditional compilation flags and their meanings.
    • Files that are machine dependent
    • Paths to reused components
    • History information about the current and previous releases
    • Information about existing major bugs and fixes
    • A brief description of new features added to the system
6.10 Makefiles
  • Makefiles are used on some systems to provide a mechanism for efficiently recompiling C++ code. With makefiles, the make utility recompiles files that have been changed since the last compilation. Makefiles also allow the recompilation commands to be stored, so that potentially long CC commands can be greatly abbreviated.
  • The makefile:
    • Lists all files that are to be included as part of the program.
    • Contains comments documenting what files are part of libraries.
    • Demonstrates dependencies, e.g., source files and associated headers using implicit and explicit rules.
6.11 Standard Libraries
  • A standard library is a collection of commonly used functions combined into one file. Examples of function libraries include <iostream> which comprises a group of input/output functions and <math> which consists of mathematical functions. When using library files, include only those libraries that contain functions that your program needs. You may create your own libraries of routines and group them in header files. 
  • Use C++ standard libraries, instead of C libraries, whenever possible, unless it is more efficient to use C libraries.
6.12 Use of Namespaces

The use of namespaces minimizes potential name clashes in C++ programs and libraries and eliminates the use of global types, variables, etc.
Clashable names include: external variable names, external function names, top-level class names, type names in public header files, class member names in public header files, etc. (Class member names are scoped by the class, but can clash in the scope of a derived class. Explicit scoping can be used to resolve these clashes.)

It is no longer necessary to have global types, variables, constants and functions if namespaces are used. Names inside namespaces are as easy to use as global names, except that you sometimes must use the scope operator. Without namespaces it is common to add a common identifier as a prefix to the name of each class in a set of related classes.

It is recommended not to place using <namespace> directives at global scope in a header file; instead place it in a source file. This can cause lots of magic invisible conflicts that are hard to track since it will make names globally accessible to all files that include that header, which is what we are trying to avoid. Inside an implementation file, using directives are less dangerous and sometimes very convenient. On the other hand, too-frequent use of the scope operator is not recommended. The difference between local names and other names will be more explicit, but more code needs be rewritten if the namespaces are reorganized.

6.13 Standard Template Library (STL)

Use Standard Template Library components, when available.

6.14 Using the new Operator

The specification for operator "new" was changed by the standardization committee, so that it throws an exception of type std::bad_alloc when it fails. Therefore, the code may catch this exception, rather than check for a NULL value. e.g.

#include <stdexcept>
int someFunction() { try { SomeClass *someClassList = new SomeClass[size]; } catch (std::bad_alloc &ex) { … } } 

  • No labels