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.
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.
:
Each file must start with a copyright notice.
:
Each file must contain an RCS identifier.
:
The common header file NFRA.h should always be included first.
:
Use angle brackets to include a header file.
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.
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.
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 filesIn 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!!!
:
File types have to be .h, .cc and .icc.
:
Declare and implement only one class in a file.
:
File names have to be unique in as large a scope as possible.
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.
:
A header file has to be guarded against multiple inclusion.
:
Forward declare classes as much as possible.
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; };
:
Use // for comments.
:
All comments have to be written in English.
:
A header file has to contain comments describing class and functions.
:
Document the functions needed for a template argument.
:
Use #ifdef iso. /*...*/ to uncomment code.
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:
Comment delimiters should not be used to outcomment code. Instead #ifdef should be used like:
#ifdef SOMESILLYNAME ... code ... #endif
:
Use descriptive names and use capitals as needed.
:
Use the prefix "its" for names of class member variables.
:
Use the prefix "their" for names of static class member variables.
:
Use the prefix "the" for names of global variables.
:
Use a meaningful prefix (a verb) for names of boolean functions.
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()) { ... }
:
Give names to arguments in function declarations.
:
Each function argument should be specified on a separate line.
:
Do not use variable argument lists (...).
:
Pass objects by reference; pass builtin data types by value.
:
Declare arguments passed by reference or pointer const when
not changing them.
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.
:
Put inline functions in file "Class.icc".
:
Inline functions only when needed for performance.
:
The keyword "inline" should be used in both declaration and
implementation.
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> #endifNote 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.
:
Declare local variables at the point where they are needed.
:
Take care of variables declared in a for-statement.
:
Use braces in a case label in a switch statement.
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: ... }
:
Use only one declaration or statement per line.
:
The * or & should immediately follow the type.
:
Use braces and indentation in the proper way.
:
Always use braces in if, for and while statements.
:
Initialize member variables with a constructor initializer list.
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:
class someName { public: function1 (); };
int functionName () { statements }
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 (begin; end; increment) { .... }
while (condition) { .... }
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.
:
Use the keyword explicit to denote that a constructor should
not automatically convert.
:
Define conversion operators only when really needed.
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.
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.
:
Never use malloc and free.
:
Set a pointer to 0 after deleting an object.
:
Use [] when deleting an array of objects.
:
A class allocating an object should also destruct it.
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.
:
Do not use pointer-to-member.
:
Do not use multiple inheritance.
:
Do not use the keyword "register".
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.
:
Global variables should not be used if possible; use static class
member variables.
:
Declare enums and typedefs in a class.
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);
:
Declare data variables only as private and at the end of the class.
:
Declare functions in the order public-protected-private.
:
A class should contain default constructor, copy constructor,
destructor, and assignment operator.
:
A destructor has to be declared virtual in a base class.
:
Declare member functions const when no changes are made.
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) }
:
Do not use = when constructing an object.
:
Always test for self-assignment in the assignment operator.
:
Use 0 (not NULL) when assigning or testing pointers.
:
Use a typedef to define a pointer-to-function.
:
Use unsigned when a variable cannot have negative values.
:
Use symbolic names for constants.
:
Use enum or static const members iso. #define.
:
Use pre- and postconditions in a function.
:
Use a temporary variable instead of a function call in loop guards.
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.
String str(5);which indeed looks like a constructor.
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; }