General C++ Coding Standard at the NFRA

Reference:
SEG5.1
Author:
Ger van Diepen
Date:
10 October 1997
Version
1.1

Leader

Contents

Version History

Version 1.0 (13 October 1995)
Initial version
Version 1.1 (10 October 1997)
Added rule 15.9. Updated distribution list.

Distribution List

Introduction

Purpose

This document defines the style of programming in C++ at the NFRA. The rules and recommendations presented are global. Each project can have its own programming standard derived from this standard. However, reasons for deviations from this global standard should be clearly documented.

On the WWW this document can be found on http://www.nfra.nl/~seg/cppStdDoc.html.
A summary of all rules and recommendations is available on http://www.nfra.nl/~seg/cppStd.html.

The global standard is not fixed, but may evolve over time as the C++ language changes and as new insights arise. Especially the upcoming C++ standard (as shown in the Working Paper) may affect it.

Audience

The document is intended for all project leaders of projects where C++ code will be developed.
Furthermore project documents may refer to it and as such it can also be used by all programmers writing in C++.

Motivation

C++ is a powerful, but difficult language. It has a sometimes obscure syntax and it is easy to forget things or to make mistakes.

A common set of rules and recommendations result in code which is easier to comprehend and to maintain. It also results in better code, because they let the programmer avoid common C++ pitfalls and tell about useful C++ constructs.

References

Programming in C++, Rules and Recommendations
FN/Mats Henricson and Erik Nyquist . Copyright (C) 1990-1992 by . Ellemtel Telecommunication Systems Laboratories . Box 1505 . 125 25 Alvsjo . Sweden . Tel: int + 46 8 727 30 00 . Permission is granted to any individual or ...
--- [709] http://www.algonet.se/~erny/RULES/RULES.HTM (163K)
AIPS++ Programming Standards and Guidelines
AIPS++ Note 167 by Paul Shannon, NRAO (pshannon@nrao.edu)
The AIPS++ Code Review Process
AIPS++ Note 170 by Paul Shannon, NRAO (pshannon@nrao.edu)
An Abbreviated C++ Code Inspection Checklist
John T. Baldwin (johnb@searchtech.com)
C++ Working Paper
C++ Standardization Committee
cxx2html documentation extractor
Darrell Schiebel (dschieb@nrao.edu)

File Headers

RULE 1.1: Each file must start with a copyright notice.
RULE 1.2: Each file must contain an RCS identifier.
RULE 1.3: The common header file NFRA.h should always be included first.
RULE 1.4: Use angle brackets to include a header file.

Each source file has to start with a copyright notice (which can be based on the AIPS++ copyright notice). In this way it is avoided that software developed at the NFRA can be used for commercial purposes.

It is good practice to know the RCS-version of a file. This can be achieved by putting the line

           //    $Id$
       
after the copyright notice in a .h file.
In a .cc file the line
           static const char* RCSid ="@(#) $Id$";
       
has to be put after the copyright line. The "$...$" string is found by the RCS "ident" program, whereas "@(#)" is found by the SCCS "what" program. With the required RCSid string, either one of these standard programs will find all RCS id's in a program, and thus the versions of all .cc files linked into the program.
Note: when the variable RCSid is not used in a program, compilers may generate a "not used" warning for it. This can be ignored.

The common header file "NFRA.h" contains some general #defines and maybe other stuff. This file should always be included as the first header file.

       #include <NFRA.h>
       #include other files
       
In a project this file could be included in a project specific common header file (e.g. tms.h).

Angle brackets should be used to include header files. Compilers (e.g. ObjectCenter) may have problems to handle quoted header files correctly. Thus:

       #include <someFile.h>             // correct
       #include "someFile.h"             // INCORRECT!!!
       

Class Files

RULE 2.1: File types have to be .h, .cc and .icc.
REC 2.2: Declare and implement only one class in a file.
REC 2.3: File names have to be unique in as large a scope as possible.

The file XXX.h contains the declaration of class XXX.
The file XXX.cc contains the implementations of the non-inlined functions in class XXX.
The file XXX.icc contains the implementations of the inline functions of class XXX.

It is best to declare only one class in a file and to name the files after the class (followed by .h, .icc, and .cc). This makes it easier to find the files declaring and implementing a class. Furthermore, some compilers have problems with a mix of templated and non-templated classes in the same file. This also calls for one class per file.

However, sometimes a class has a helper class which is very strongly related to the class. In that case it makes sense to put both classes in the same file.

Sometimes it makes sense to use multiple files for the implementation. (For example, the linker under UNIX links in the entire object file. So when a class is large, its resulting large object file will be linked in completely). In that case the implementation file could be named "Class_n.cc", where n is a sequence number 0,1,2,...

File names should be unique, even for files in different subdirectories. Some compilers may generate (initialization) functions with names generated from the file name.


Header Files

RULE 3.1: A header file has to be guarded against multiple inclusion.
RULE 3.2: Forward declare classes as much as possible.

The header file has to contain a guard to avoid multiple inclusion. The name of the guard should be the capitalized file name with the "." replaced by a "_". The guard should be put after the copyright statement.

The class and the functions should be properly documented. The AIPS++ tool cxx2html will be used to extract documentation from a header file as an HTML file. This requires that the documentation follows the cxx2html standards.

When possible, classes used in function and member declarations have to be forward declared instead of including their header files. This will reduce the header file dependencies, thus reduce the number of compilations required when a header file gets changed.
In general a forward declaration of a class is possible when an object of the class is only declared by reference or pointer. When a class is used in inheritance or as a data member, the appropriate header file needs to be included directly in the .h file of the class.

The general layout of a header file MyClass.h will look like:

       // Copyright notice

       #if !defined(MYCLASS_H)
       #define MYCLASS_H

       // $Id$

       // Includes
       #include <NFRA.h>>;
       #include <AipsIO.h>;

       // Forward Declarations.
       class String;
       template<class T> class Array;

       // Description of MyClass.
       template<class T> class MyClass
       {
           // Constructor using the forward declared classes.
           // Description of this function.
           MyClass (const String& name,
                    const Array<T>& array);

           // This data member requires inclusion of AipsIO.h
           AipsIO io_p;
       };
       

Comments

RULE 4.1: Use // for comments.
RULE 4.2: All comments have to be written in English.
RULE 4.3: A header file has to contain comments describing class and functions.
RULE 4.4: Document the functions needed for a template argument.
RULE 4.5: Use #ifdef iso. /*...*/ to uncomment code.

Comments (which have to be given using //) should be used in the header files to make the purpose of classes and functions clear.

The class description should tell the purpose of the class. In general, it should also contain an example explaining how the class can be used. When the class is templated, it should also tell which functions are needed for each template parameter. For example, a templated sort function may require "operator==" and "operator>" for the data to be sorted. It may also require the default constructor, copy constructor and assignment operator.

The rules of the AIPS++ tool cxx2html have to be followed t oallow for automatic extraction of documentation. Cxx2html allows the use of all html-tags supplemented with some cxx2html-specific tags. Some commonly used tags are:

<module name=...>
Define a module (i.e. collection of classes).
<group name=...>
Define a group of global functions.
<summary>
One line describing module, class or group.
<use visibility=local/export/exception>
Define if module, class or group is internal or external.
<synopsis>
Describe the purpose of the module, class or group.
<example>
To give an example how to use classes and functions.
<srcblock>
Block of source code (like <pre>, but takes care of < and >).
<src>
Piece of source code (like <code>, but takes care of < and >).
<templating>
Describe with <li>'s the functions required for a template parameter.

Comment delimiters should not be used to outcomment code. Instead #ifdef should be used like:

       #ifdef SOMESILLYNAME
           ... code ...
       #endif
       

Names

RULE 5.1: Use descriptive names and use capitals as needed.
REC 5.2: Use the prefix "its" for names of class member variables.
REC 5.3: Use the prefix "their" for names of static class member variables.
REC 5.4: Use the prefix "the" for names of global variables.
REC 5.5: Use a meaningful prefix (a verb) for names of boolean functions.

The names of classes, functions, variables, typedefs, and enumerations have to be descriptive. Acronyms should be avoided, unless they are commonly agreed on.

Names of classes, typedefs, and enumerations have to start with a capital. Function and variable names have to start with a lowercase letter. When a name consists of multiple words, the words after the first one have to start with a capital.

When macros are used, their names should consists of uppercase letters only. Underscores can be used to make their names more readable.

A double underscore (__) should never be used, since it is commonly used by compiler writers.

The prefix "its" should be added to the name of a class member variable, while the prefix "their" should be used for a static class member variable. This makes it clear in the implementation of a function that a variable used is a (static) class member variable.

The prefix "the" should be used for global variables. However, note that global variables should in principle not be used. It is better to use static member variables instead.

For example:

       class MyClass
       {
       public:
           // Constructor
           MyClass();
       
           // A function.
           void exampleFunctionName();

       private:
           float itsClassVariable;
           static double theirShape;
       };
       

In an if-statement the names of boolean functions should read like normal English. This will usually require a verb as a prefix. For example:

       if (object.isValid()) {
           ...
       }
       if (object.hasData()) {
           ...
       }
       if (object.canHandleRequests()) {
           ...
       }
       

Function Arguments

RULE 6.1: Give names to arguments in function declarations.
RULE 6.2: Each function argument should be specified on a separate line.
REC 6.3: Do not use variable argument lists (...).
REC 6.4: Pass objects by reference; pass builtin data types by value.
RULE 6.5: Declare arguments passed by reference or pointer const when not changing them.

When declaring a function each argument should get a name making clear the purpose of the argument. The name should be the same as used in the implementation of the function.

When a function has multiple arguments, each argument should be put on a separate line and properly indented. In this way optional inline comments can be given for clarification.

For example:

       class MyClass
       {
       public:
           // function comments
           MyClass (const String& name);
       
           // function comments
           const String& name() const;
       
           // function comments
           void rename (const String& oldName,       // argument comment
                        const String& newName);
       };
       
Avoid the use of variable argument lists (the ... notation), because they are inherently type-unsafe.

Passing an object to a function should always be done by reference. Passing by value means that the copy constructor will be called, which is too expensive. Passing by pointer has the meaning that an array of objects is passed.
Variables with a builtin data type (int, float, ...) can be passed by value or by reference. On most systems passing by value is (slightly) faster.
In a templated function it is not known whether the argument is an object or a variable with a builtin data type. In this case it should always be passed by reference.

Function arguments passed by reference (or pointer) should be declared const when no changes are made to them. Otherwise, the use of such functions is prohibited for const objects.

Note that passing by value means that a copy of the original argument is passed. This implies constness for the original argument, but not for the copy.

       int findLastZero (float* array,
                         unsigned int length)
       {
           while (length > 0) {
               length--;
               if (array[length] == 0) {
                   return length;
               }
           }
           return -1;
       }
       
In this example the length argument is directly used. This is possible because it is passed by value, so length is only a copy of the caller's value.
Note that length could also be declared as "const unsigned int". In that case the compiler would give errors; it is not allowed to change length.

Inline Functions

RULE 7.1: Put inline functions in file "Class.icc".
REC 7.2: Inline functions only when needed for performance.
RULE 7.3: The keyword "inline" should be used in both declaration and implementation.

Only simple and small (usually one-line) functions which are essential for performance should be inlined.

When compilers cannot inline a function, they may turn it into a static function which get linked in multiple times. This can result into an enormous increase in the size of the executable.

Debuggers may not be able to debug inlined functions. Therefore it makes sense to outline them for debug purposes and inline them for production purposes. This can be achieved by putting them into a separate file with name "Class.icc" and including that file into the .h file and .cc file as follows:

       // At the end of Class.h
       #if !defined(DEBUG_MODE)
       #include <Class.icc>
       #endif

       // At the end of Class.cc
       #if (defined(DEBUG_MODE)
       #include <Class.icc>
       #endif
       
Note that in the common header file NFRA.h the keyword "inline" is #defined as an empty string when DEBUG_MODE is defined. Usually "inline" needs to be used in the implementation only. However, some compilers require it to be used at the declaration of inlined templated functions. Therefore it is good practice to use it always at declaration and implementation.

Local Variables

REC 8.1: Declare local variables at the point where they are needed.
REC 8.2: Take care of variables declared in a for-statement.
REC 8.3: Use braces in a case label in a switch statement.

It is good practice to declare a local variable only when it is needed. In this way unnecessary and possibly expensive constructor calls can be avoided.
       void MyClass::someFunction (...)
       {
           if (itsErrorFlag) {
               return;
           }
           String string;     // in this way String is not created
                              // when not needed
              ...
       }
       
It may also make sense to use braces to reduce the scope of an object. This ensures that the object is destructed as early as possible. In the future compilers may be able to detect this themselves, but currently compilers destruct objects only at the end of a block.

It is possible to declare the loop variable in the for-block. If declared that way, the ARM says that the loop-variable is still visible after the loop. However, in the newly proposed C++ standard the loop-variable will be invisible. To be visible, the loop-variable has to be declared before the loop. E.g.

           for (int i=0; i<n; i++) {
                 ...
           }
           // At this point i is still known according to ARM,
           // but not according to the new standard.
       
If the loop-variable is used after the loop, it should be declared before the loop and not in the for-statement itself.

Often compilers cannot directly handle the creation of an object in a case-statement. To be able to do this, braces has to be used.

           switch (itsOption) {
           case (anOption):
             {                         // use braces to be able
               Array<float> array;     // to create the array.
               ...
             }
             break;
           default:
             ...
           }
       

Coding Style

RULE 9.1: Use only one declaration or statement per line.
RULE 9.2: The * or & should immediately follow the type.
RULE 9.3: Use braces and indentation in the proper way.
RULE 9.4: Always use braces in if, for and while statements.
REC 9.5: Initialize member variables with a constructor initializer list.

A line in a source file should contain only one declaration, definition or statement. This ensures that:
- the code is easier to read.
- no mistakes are made with pointer declarations (see below).
- the debugger can step a statement at a time (because it steps lines).

When declaring a pointer (or reference) type, it is clearer to make the * or & part of the type. This agrees with most present day C++ coding standards. However, C++ (and C) does not treat them that way.

           char* p1, p2;
       
declares p1 as a pointer to char, but p2 as a char. Therefore the rule should be followed to declare only one variable per line.
           char* p1;
           char* p2;
       

Braces and indentation should be used in a proper way following the K&R conventions. The various constructs should be done as follows:

Classes
	      class someName
	      {
	      public:
	          function1 ();
	      };
	      
Functions
	      int functionName ()
	      {
	          statements
	      }
	      
If-then-else
	      if (condition) {
	          ....
	      }else{                            // or "} else {"
	          ....
	      }
	      
Always use braces, even if there is only one statement. This makes it possible to add statements to a branch, without getting surprised.
For-loop
	      for (begin; end; increment) {
	          ....
	      }
	      
While-loop
	      while (condition) {
	          ....
	      }
	      
The emacs editor in c++-mode supports this coding style very well.

In a constructor the member variables should be initialized with an initializer list. In this way things are made clear. Furthermore unnecessary constructor calls are avoided. In accordance to the rule of only one statement per line, only one initializer should be given per line following the style as shown in the example. When a class is derived from a superclass, the superclass constructor should be the first initializer. For example:

       DerivedClass::DerivedClass (arguments)
       : SuperClass (arguments),
         itsString  (someString),
         itsMember2 (someValue)
       {
           ...
       }
       
When the object was initialized as:
       DerivedClass::DerivedClass (arguments)
       : SuperClass (arguments)
       {
           itsString  = someString;
           itsMember2 = someValue;
           ...
       }
       
the compiler would call the default constructor for itsString to initialize the String member. Thereafter the assignment would be called in the function body. Usually this is more expensive than the use of the copy constructor in the initializer list.

Conversion Functions

RULE 10.1: Use the keyword explicit to denote that a constructor should not automatically convert.
REC 10.2: Define conversion operators only when really needed.

C++ allows for a one step automatic conversion of one type to another. This can be achieved in 2 ways:
  1. Using one-argument constructors. E.g. a String constructor
    	          String (char* text);
    	      
    will automatically convert every char* argument to String whenever a function is called expecting a String. Sometimes this behaviour is very nice, but sometimes it leads to very surprising results.
    Note that a constructor where the second and following arguments have default values, also counts as a one-argument constructor.

    The newly proposed C++ standard allows the use of the keyword "explicit" to denote a constructor that should not automatically convert. It is recommended to use this keyword where appropriate. E.g.

    	          explicit Vector (int);
    	      
    will (in the future) avoid automatic conversion of int to Vector. Since present-day compilers do not support explicit yet, it is #defined as an empty string in NFRA.h.
  2. Using conversion operators. They can also lead to surprising results and should therefore be used with great care.

New/Delete

RULE 11.1: Never use malloc and free.
REC 11.2: Set a pointer to 0 after deleting an object.
RULE 11.3: Use [] when deleting an array of objects.
REC 11.4: A class allocating an object should also destruct it.

The operators new and delete should be used to allocate and deallocate storage. The functions malloc and free are type-unsafe. What is more important, they do not call constructors and destructors. Also avoid using functions doing an implicit malloc (e.g. strdup).

A pointer should be set to 0 when an object is deleted via that pointer. In this way it is avoided that the pointer is dangling. Using the pointer again will immediately result in a segmentation violation instead of undefined behaviour. Furthermore, the object will not be deleted twice when the delete operator is used again on that pointer.
When deleting in the destructor, setting to 0 is not necessary, because the pointer will disappear.

To call the destructor on all objects in an array, the array must be deleted with [].

           String* ptr = new String[100];
           delete [] ptr;
           ptr = 0;
       
When a class allocates storage, it should also deallocate it. In general, it leads to surprising behaviour when the user of a class has to take care of deleting storage which he did not allocate.

Things to Avoid

REC 12.1: Do not use pointer-to-member.
REC 12.2: Do not use multiple inheritance.
REC 12.3: Do not use the keyword "register".

Pointer-to-member is not easy to use. Furthermore, some compilers do not support it correctly. However, it is easy to have a pointer to a static member function, because such a pointer is a normal pointer-to-function.

Opinions are divided about multiple inheritance. If possible, it is better not to use it, because it can be tricky to initialize the base classes correctly.

The keyword "register" does not need to be used nowadays. The compilers are capable of determining themselves whether it makes sense to hold a variable in a register. It only clutters the code.


Namespace

REC 13.1: Global variables should not be used if possible; use static class member variables.
REC 13.2: Declare enums and typedefs in a class.

It is hard to ensure that a global name is unique. Therefore it is recommended to use static class members and to declare typedefs and enumerations in a class. Another important advantage is that it shows clearly where such a variable or enum belongs. E.g.
       class Sort
       {
       public:
           // Define the signature of the compare function.
           // It should return  -1   when left < right
           //                    0   when left == right
           //                    1   when left > right
           typedef int (CompareFunc*) (const void* left, const void* right);

           // Define the possible sort order for a sort key.
           enum Order {
               Ascending = 1,
               Descending = 2
           };

           // Define a sort key.
           // Note that the scope Sort:: does not need to be used here.
           void sortKey (const void* data, CompareFunc, Order = Ascending);
         ...
       }

       // Use this sort class.
       // ObjectCompare is a templated function defined elsewhere.
       // Here the scope Sort:: has to be used for the Sort::Order argument.
       Int* someData;
       sort.sortKey (someData, ObjectCompare<Int>, Sort::Descending);
       

Class Declarations

RULE 14.1: Declare data variables only as private and at the end of the class.
RULE 14.2: Declare functions in the order public-protected-private.
REC 14.3: A class should contain default constructor, copy constructor, destructor, and assignment operator.
RULE 14.4: A destructor has to be declared virtual in a base class.
RULE 14.5: Declare member functions const when no changes are made.

Public functions are the items of most interest to the user, so they should appear first. The variables are of least importance, so they should appear last. This makes it also easier to find them.

To enhance maintainability, variables should always be declared private. If a variable is to be used by a derived class, a protected, inlined accessor function should be defined to allow access to the data.

It is good practice to include a default constructor. This makes allocation of an array of those objects possible. Also templated classes often use the default constructor of a template parameter. However, when it makes no sense to have a default constructor, it can be left out.
The copy constructor, assignment operator and destructor should always be declared, even if they do not have to do anything. In that way it is clear that they are not accidently forgotten. Especially when pointers are kept inside an object, these functions are necessary to ensure that the data inside the object are copied and deleted correctly. Sometimes it makes sense to forbid the copy constructor and assignment operator. In that case they should be declared private and no implementation should be made in the .cc file.
A destructor should always be present to ensure proper destruction of the object. When a class is a base class for derivation, its destructor should be declared virtual. Otherwise the destructor of a derived class will not be called when such an object is deleted via a pointer to the base class.

       BaseClass* ptr = new DerivedClass();
       delete ptr;
       
In the above example the destructor of DerivedClass will not be called if the destructor in BaseClass is not virtual.

A member function should be declared const when it does not change the object and when it does not return a non-const member.

       class String
       {
       public:
           // Construct from a char*.
           String (const char* string);

           String (const String& that);

           ~String();
       
           String& operator= (const String&);

           // Get the string length.
           // It does not change the object, thus a const function.
           unsigned int length () const;

       protected:
           // Give access to the string (for derived classes).
           // It returns a non-const member, thus the function is non-const.
           char* chars ();

       private:
           unsigned int itsLength;
           char*        itsString;
       
Note that it is possible to overload a function on constness (which can be very useful).
       template<class T> class Vector
       {
       public:
           // Get const access to the data value at the given index.
           const T& operator[] (unsigned int index) const;

           // Get non-const access to the data value at the given index.
           T& operator[] (unsigned int index);
       };

       void someFunction (const Vector<int>& vector)
       {
           int value = vector[0];         // okay (takes const version)
           vector[0] = 0;                 // error (vector is const)
       }
       void someFunction (Vector<int>& vector)
       {
           int value = vector[0];         // okay (takes non-const version)
           vector[0] = 0;                 // okay (vector is non-const)
       }
       

Good Practice

REC 15.1: Do not use = when constructing an object.
RULE 15.2: Always test for self-assignment in the assignment operator.
RULE 15.3: Use 0 (not NULL) when assigning or testing pointers.
RULE 15.4: Use a typedef to define a pointer-to-function.
RULE 15.5: Use unsigned when a variable cannot have negative values.
REC 15.6: Use symbolic names for constants.
REC 15.7: Use enum or static const members iso. #define.
REC 15.8: Use pre- and postconditions in a function.
REC 15.9: Use a temporary variable instead of a function call in loop guards.

The syntax of C++ allows to use = when constructing an object. For example:
       String str = 5;
       
This looks like an assignment, but it is a constructor! The reason for this is to be compatible with C which allows the initialization of a variable at its declaration.
To avoid misconceptions, it is strongly recommended to write it as:
       String str(5);
       
which indeed looks like a constructor.
However, parentheses should not be used when constructing an object with the default constructor. E.g.
           SomeClass xx;             // correct
           SomeClass xx();           // incorrect!!
       
The latter looks like constructing xx, but is actually the declaration of a function xx returning a SomeClass object.

Note that the assignment-like initialization can be used for builtin data types (like int, float).

The implementation of the assignment operator should always test for self-assignment.

       String& String::operator= (const String& that)
       {
           if (this == &that) {
               return *this;
           }
           delete itsString;
           itsString = 0;
           itsLength = 0;
           itsString = new char[that.length() + 1];
           itsLength = that.length();
           return *this;
       }
       
Without the test for self-assignment, assignment would fail when assigning a String to itself.

Pointers in C++ should be compared with or set to 0. This is unlike C, which uses NULL.

When an integer variable cannot be negative, it is good practice to make it unsigned. Especially when used with function arguments, it makes it clear to the user that negative values cannot be given. However, beware for an often made error in reverse loops where a test is made on >= 0.

       int findLastZero (float* array,
                         unsigned int length)
       {
           for (length--; length>=0; length--) {
               if (array[length] == 0) {
                   return length;
               }
           }
           return -1;
       }
       
This loop will never end, because length will never get negative.

The pointer-to-function syntax is somewhat difficult. To make life easier, a typedef should be used to define a pointer to a function.

       typedef int CompareFunction (const void*, const void*);
       void sort (void* data,
                  CompareFunction* functionPointer);
       
or
       typedef int (*CompareFunctionPointer) (const void*, const void*);
       void sort (void* data,
                  CompareFunctionPointer functionPointer);
       
Both ways are equivalent. The difference is that in the second example the pointer is included in the typedef, while in the first one it is included in the function declaration.

Constants should not be used as such. Use of a symbolic name (using an enum or static const) is much better. Exceptions from this are the constants 0 and 1 which are often used as initializers or increments.
Preferably a symbolic name is not defined with a #define, because this is type-unsafe.

The use of pre- and postconditions in a function can make debugging much easier. A precondition should check if the expected function inputs and object states are correct. An example is boundary checking in an array. The postcondition should check if the resulting object state is correct. The assert macro can be used with it. Because execution of such functions can be expensive, they should be compiled conditionally.

       void SomeClass::someFunction ()
       {
       #if defined(DEBUG_MODE)
           check precondition
       #endif
           ...
           execute function
           ...
       #if defined(DEBUG_MODE)
           check postcondition
       #endif
       

Compilers cannot always detect that the value used in the end-test in a for (or while) loop does not change. E.g.

       Vector vector(10);
       for (uInt i=0; i<vector.nelements(); i++) {
           vector(i) = i;
       }
       
results in a call to Vector::nelements for each iteration, because the compiler cannot know that the number of elements in the vector does not change by the non-const vector indexing operation in the subsequent statement. Therefore it is better to use a temporary variable.
       Vector vector(10);
       uInt n = vector.nelements();
       for (uInt i=0; i<n; i++) {
           vector(i) = i;
       }