Code Structure Strategy
Introduction
Coding standards are crucial to enable humans understand the code. Both code they wrote, and code written by other developers. This page documents code standards we adhere to in Chaste. If you are writing Chaste code, please follow them!
What to put in .hpp and .cpp files
A normal class should be declared in a .hpp
file and implemented
separately in a .cpp
file. For example, the definition of MyClass
in MyClass.hpp
would look like:
and the implementation in MyClass.cpp
would look like:
Where possible, a templated class should also be defined in a .hpp
file
and implemented in a .cpp
file, using explicit instantiation (see below).
If this is not possible, then the class should be defined at the top of
the .hpp
file and implemented separately underneath, to avoid implicitly
inlining methods where this is not appropriate, and to ease reading of the
code (a method defined within a class declaration is implicitly marked
as inline, although a compiler may choose to ignore this suggestion if
it considers the method to be too large.)
Policy on includes
Good use of the #include
statement helps to keep code tidy, and results
in shorter compilation times as a bonus.
As a general rule of thumb, if a
.cpp
file (sayA.cpp
) makes use of a class (sayB
), it should#include
the header (B.hpp
) for that class.One exception to this is if the class is also used in
A.hpp
(becauseB
is the base class ofA
, orB
is used as an argument for one ofA
’s methods). In that case the#include
should appear inA.hpp
and may be omitted fromA.cpp
.Header files should not contain any
#include
s apart from those cases indicated in the previous point. Really. It may seem more convenient, but it just makes life difficult later on (We’ve been bitten by this several times).Older versions of
g++
aren’t very picky about whether you need to include standard library headers (e.g.<cstring>
). You should still try to remember to include them, as otherwise your code won’t compile on newer versions.You should include the C++ versions of C standard library headers, e.g.
<ctime>
rather than<time.h>
.
Point 2 can even be expanded upon. Often you might not need to include
B.hpp
in A.hpp
, even in the cases mentioned there – instead just using
a forward declaration of B
: “class B;
”. In this case A.cpp
will need
to #include
B.hpp
. This technique is useful for breaking circular
include chains, and this is currently the only situation in which we use it,
as widespread use would mean many more #include
lines. We may revisit
this decision, as it can reduce compilation time.
Some includes, particularly of the Boost Ublas and Serialization libraries,
also need to be given in a particular order. Generally Ublas headers
(#include "UblasIncludes.hpp"
or #include "UblasCustomFunctions.hpp"
)
should come first – in particular they must appear before any PETSc headers
(or some PETSc/Boost version combinations might break e.g. PETSc 2.2/Boost 1.33.1).
See Also
- Boost Serialization for details on the archiving headers.
- Include What You Use for more about this kind of includes policy.
Explicit instantiation for templated classes
We use explicit instantiation for templated classes wherever possible.
For example, if MyClass
is templated over spatial dimension:
then in the .cpp
file, we define the methods for all spatial dimensions expected:
C++ initialisers
We use C++ initialisers to initialise member variables. This is because when
a class is instantiated, all member variables are initialised before the
constructor is called. If they aren’t initialised by the initialiser list,
they are given default values. For things like int
s this isn’t much of a
problem, but for objects it then requires the corresponding class to have
a default constructor, and you get the unnecessary overhead of creating an
instance of the class twice.
As an example, we prefer this:
to this
Where to put common code that is used just for tests
If classes are only used by a single test file, they should remain in that
test file. However, if multiple test files have very similar helper classes,
then we should refactor the commonality into a class in a shared file.
For tests only used by one component, this file can be located in the
tests
folder. If the code is of use to tests of multiple components,
then a suitable location is within the fortests
folder in the src
folder.
Documenting the code with Doxygen
We use the doxygen package to automatically generate HTML documentation from comments in the Chaste code. More information on doxygen commands can be found in the doxygen manual.
We document each class, as well as each member and method in that class, by adding a doxygen code block of the form:
just before the code being documented (we tend to place all such documentation
in the header file where the class is defined, although this doesn’t matter).
To document inputs and outputs to a particular method, we use the
commands @param
and @return
, as shown by the following example
from AbstractElement.hpp
:
In addition to the standard doxygen commands, we have added the ability to
completely ignore auto-generated files (for example, those such
as BackwardEulerFoxModel2002Modified.hpp
that are processed by chaste_codegen).
Such files should include the doxygen block
Excluding lines from coverage testing
Caution
Generally to be avoided. If at all possible, write a test to cover each line of code!
If you really need to exclude some code from coverage you can use special comments provided by lcov settings:
for a single line of code, or
for a block of code. For instance:
A common use-case of this is a hard-exit which indicates an error
(likely in programming rather than usage), which we indicate with
NEVER_REACHED
as per Exception Strategy – this
line is automatically excluded from coverage testing.
Other Conventions
If no advice is offered on the Chaste Strategies pages, we informally follow many/most of the Joint Strike Fighter C++ coding standards.