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:
class MyClass
{
private:
int mVariable; // a member variable
public:
MyClass(int i); // a constructor
}and the implementation in MyClass.cpp would look like:
#include "MyClass.hpp"
MyClass::MyClass(int) : mVariable(i)
{
// Do something here
}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
.cppfile (sayA.cpp) makes use of a class (sayB), it should#includethe header (B.hpp) for that class.One exception to this is if the class is also used in
A.hpp(becauseBis the base class ofA, orBis used as an argument for one ofA’s methods). In that case the#includeshould appear inA.hppand may be omitted fromA.cpp.Header files should not contain any
#includes 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:
template<unsigned DIM>
class MyClass
{
private:
int mVariable; // a member variable
public:
MyClass(int i); // a constructor
}then in the .cpp file, we define the methods for all spatial dimensions expected:
#include "MyClass.hpp"
template<unsigned DIM>
MyClass<DIM>::MyClass(int) : mVariable(i)
{
// Do something here
}
template class MyClass<1>;
template class MyClass<2>;
template class MyClass<3>;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 ints 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:
MyClass(int i) : mVariable(i)
{
// Do something
}to this
MyClass(int i)
{
mVariable = i;
// Do something
}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:
/**
* This is a doxygen code block
*/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:
/**
* Get the node with a given local index in this element.
*
* @param localIndex local index of the node in this element
* @return a pointer to the node.
*/
Node<SPACE_DIM>* GetNode(unsigned localIndex) const;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
/** @autogenerated */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:
// LCOV_EXCL_LINE
for a single line of code, or
// LCOV_EXCL_START
<uncovered code>
// LCOV_EXCL_STOP
for a block of code. For instance:
std::string GetCurrentWorkingDirectory()
{
fs::path cwd;
try
{
cwd = fs::current_path();
}
// LCOV_EXCL_START
catch (...)
{
// Not sure what could cause this, so not covered, but just in case...
EXCEPTION("Unable to determine current working directory");
}
// LCOV_EXCL_STOP
return cwd.string();
}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.
