Software Coding Standards
Multiphysics Object Oriented Simulation Environment (MOOSE) uses ClangFormat for formatting all C++ code in the repository. The application of the format is enforced and checked when the a code is contributed, see Contributing for more details. The configuration file for the formatting is provided in the .clang-format file. If clang is installed, the following command will automatically format code changed between your current branch and the supplied <branch>
(if omitted, defaults to HEAD).
git clang-format [<branch>]
The automated testing that occurs on all Pull Requests will produce a "diff" of changes needed to conform with the standard.
General style guidelines include:
Single spacing around all binary operators
No spacing around unary operators
No spacing on the inside of brackets or parenthesis in expressions
Avoid braces for single statement control statements (i.e for, if, while, etc.)
C++ constructor spacing is demonstrated in the bottom of the example below
File Layout
Header files should have a ".h" extension
Header files always go either into "include" or a sub-directory of "include"
C++ source files should have a ".C" extension
Source files go into "src" or a subdirectory of "src".
Files
Header and source file names must match the name of the class that the files define. Hence, each set of .h and .C files should contain code for a single class. Helper classes or classes not exposed to the user are an exception to this rule.
src/ClassName.C
include/ClassName.h
Naming
ClassName
Class names utilize camel case, note the .h and .C filenames must match the class name.methodName()
Method and function names utilize camel case with the leading letter lower case._member_variable
Member variables begin with underscore and are all lower case and use underscore between words.local_variable
Local variables are lowercase, begin with a letter, and use underscore between words
Use of C++ auto
keyword
Use auto
unless it complicates readability, but do not use it in a function or method declaration. Make sure your variables have good names when using auto!
auto dof = elem->dof_number(0, 0, 0);
auto & obj = getSomeObject();
auto & elem_it = mesh.active_local_elements_begin();
auto & item_pair = map.find(some_item);
// Cannot use reference here
for (auto it = obj.begin(); it != obj.end(); ++it)
doSomething();
// Use reference here
for (auto & obj : container)
doSomething();
Index Variables in Looping Constructs
In keeping with good auto
practices, it would be really nice if we could use auto
for loop indices in places where range loops aren't convenient or possible. Unfortunately, the most natural code one would like to write yields a warning with our default compiler flags:
for (auto i = 0; i < v.size(); ++i) // DON'T DO THIS!
warning: comparison of integers of different signs: 'int' and 'std::__1::vector<int, std::__1::allocator<int> >::size_type' (aka 'unsigned long')
This warning stems from the fact that numeric literals in C++ are treated as "signed ints". There are suffixes that can be applied to literals to force the "correct" type, but are you sure you really know the correct type? If you are thinking "unsigned int" you are in the majority and unfortunately also wrong. The right type for a container is ::size_type (aka 'unsigned long')
. See the error message once more above.
The ideal solution would be to match the type you are looping over, but this is slightly more difficult than just using decltype
due to qualifiers like const
. In MOOSE we have solved this for you with MooseIndex
:
// Looping to a scalar index
for (MooseIndex(n_nodes) i = 0; i < nodes; ++i)
... Do something with index i
// Looping to a container size (where range-for isn't sufficient)
for (MooseIndex(vector) i = 0; i < vector.size(); ++i)
... Do something with index i
Lambdas
List captured variables (by value or reference) in the capture list explicitly where possible.
std::for_each(container.begin(), container.end(), [=local_var](Foo & foo) {
foo.item = local_var;
});
Other C++ Notes
Use the
override
keyword on overriddenvirtual
methodsUse
std::make_shared<T>()
when allocating new memory for shared pointersUse
std::make_unique<T>()
when allocating new memory for unique pointersMake use of std::move() for efficiency when possible
Variable Initialization
When creating a new variable use these patterns:
unsigned int i = 4; // Built-in types
SomeObject junk(17); // Objects
SomeObject * stuff = new SomeObject(18); // Pointers
Trailing Whitespace and Tabs
Tabs and trailing whitespace are not allowed in the C++ in the repository. Running the following one-liner from the root directory of your repository will format the code correctly.
find . -name '*.[Chi]' -or -name '*.py' | xargs perl -pli -e 's/\s+$//'
C++ Includes
Only include things that are absolutely necessary in header files. Please use forward declarations when possible.
// Forward declarations
class Something;
All non-system includes should use quotes with a single space between include
and the filename.
#include "LocalInclude.h"
#include "libmesh/libmesh_include.h"
#include <system_library>
Documentation
In source documentation should be extensive, designed toward the software developer, and be formatted for the use of Doxygen.
Python
Where possible, follow the above rules for Python. The only modifications are:
Four spaces are used for indenting and
Member variables should be named as follows:
class MyClass:
def __init__(self):
self.public_member
self._protected_member
self.__private_member
Code Commandments
Use references instead of pointers whenever possible. - i.e., this object lives for a shorter period of time than the object it needs to refer to does
Methods should return pointers to objects if returned objects are stored as pointers and references if returned objects are stored as references.
When creating a new class: - Include dependent header files in the *.C file whenever possible (i.e. the header uses only references or pointers in it's various declarations) and use forward declarations in the header file as needed. - One exception is when doing so would require end users to include multiple files to complete definitions of child objects (Errors typically show up as "incomplete class" or something similar during compilation).
Avoid using a global variable when possible.
Every destructor must be virtual.
All function definitions should be in *.C files. - The only exceptions are for inline functions for speed and templates.
Thou shalt not commit accidental insertion in a std::map by using brackets in a right-hand side operator unless proof is provided that it can't fail.
Thou shalt use range-based loops or
MooseIndex()
based loops for iteration.