|
General C++ Coding Standard |
|
| |
You can read and download this General C++ Coding Standard coding convention and coding guideline from
here for free at your own risk. All trademarks, product names and company names or logos mentioned herein are
the property of their respective owners.
General C++ Coding Standard at the NFRA
Author: Ger van Diepen
Date: 10 October 1997
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
- Jacques van Amerongen
- Arnold van Ardenne
- Jaap Boot
- Albert Bos
- Arthur Coolen
- Teun Grit
- Michael Haller
- Johan Hamaker
- Eelke Klein
- Andre Kokkeler
- Bouke Kramer
- Jan Noordam
- Friso Olnon
- Hans van Someren Greve
- Harro Verkouter
- Marco de Vos
- Henk Vosmeijer
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
A summary of all rules and recommendations is available on http://www.nfra.nl
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 ...
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:
- 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.
- 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;
}
Don't waste time on formatting C++ source code by hand, use SourceFormatX C/C++ Code Formatter to format all C and C++ code.
|