« Back to the index


NEST Coding Style Guidelines for C++

In the code review process we want to enforce a consistent coding style to improve readability and maintainability. The article on why code readability matters gives an intuition about the benefits of readable code. To simplify the process we use different tools that check compliance with our coding style and developers can reduce the workload of the review process by checking compliance of their code on their own.

Also see our coding guidelines regarding SLI. For Python we enforce PEP8 formatting.

Note that older code might not conform to this rules and should be fixed when convenient.

Tooling

The code has to compile without warnings (in the default settings of the build infrastructure). We restrict ourselves to C++03 standard for a larger support of compilers on various cluster systems and supercomputers.

The clang-format tool is built on the clang compiler frontend. It prettyprints input files in a configurable manner, and also has Vim and Emacs integration. We supply a .clang-format configuration file to enforce some parts of the coding style. During the code review process we check that there is no difference between the committed files and the formatted version of the committed files:

Developers can benefit from the tool by formatting their changes before issuing a pull request: for fixing the formatting of a single file consider using clang-format -i <committed file> on that file. For fixing more files at once we provide a script that applies the formatting. From the source directory call:

./extras/format_all_c_c++_files.sh [start folder, defaults to '$PWD']

We use clang-format version 3.6 in the TravisCI. Older versions do not understand all formatting options we defined in .clang-format. Version 3.7 has formatting differences to 3.6.

Get clang-format: Ubuntu (see here):

# To retrieve the archive signature:
wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add -

# Repository for Trusty (14.04)
sudo sh -c 'echo "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.6 main" >> /etc/apt/sources.list'

# Repositories for Precise (12.04)
sudo sh -c 'echo "\ndeb http://llvm.org/apt/precise/ llvm-toolchain-precise-3.6 main" >> /etc/apt/sources.list'
sudo sh -c 'echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu precise main" >> /etc/apt/sources.list'
# make sure each 'deb ...' is on its own line in /etc/apt/sources.list

# install clang-format with:
sudo apt-get update
sudo apt-get install libstdc++6 libllvm3.6 clang-format-3.6

OS X:

Further we use vera++, which ‘is a programmable tool for verification, analysis and transformation of C++ source code’. It enables further checks for the code complying to the coding guidelines. We provide the nest profile file in the repository (which needs to be copied/symlinked into <vera++ home>/lib/vera++/profiles/). We then check that there are no messages generated by the execution of the following command:

vera++ -profile nest <committed file>

Get vera++: Ubuntu:

apt-get install vera++

OS X:

brew install vera++

Or consider installing vera++ from the binary packages.

Finally, we let cppcheck statically analyse the committed files and check for severe errors. We require cppcheck version 1.69 or later.

cppcheck --enable=all <committed file>

Get cppcheck: Ubuntu 15.10:

apt-get install cppcheck

Earlier versions of Ubuntu do not provide a package for cppcheck 1.69. Please follow the instructions to build cppcheck from scratch.

OS X:

brew install cppcheck
# or
sudo port install cppcheck

Build from scratch:

git clone https://github.com/danmar/cppcheck.git
cd cppcheck
git checkout tags/1.69
make PREFIX=$PWD/install/ CFGDIR=$PWD/install/cfg HAVE_RULES=yes install

# In `.bashrc` at install/bin to `PATH`:
export PATH=/path/to/cppcheck/install/bin:$PATH

Local Static Analysis

We ship a script ./extras/check_code_style.sh that lets you perform the checks on all changed files as we do during the TravisCI tasks.

$ ./extras/check_code_style.sh --help
Usage: check_code_style.sh [options ...]

Setup of Tooling is explained here:
    https://nest.github.io/nest-simulator/coding_guidelines_c++

Options:

    --help               Print program options and exit
    --incremental        Do analysis one file after another.
    --file=/path/to/file Perform the static analysis on this file only.
    --git-start=SHA      Enter the default SHA for git to start the diff
                         (default=master)
    --git-end=SHA        Enter the default SHA for git to end the diff
                         (default=HEAD)
    --nest-src=/path     The base directory for the NEST sources
                         (default=. assuming you execute check_code_style.sh
                         from the base directory.)
    --cppcheck=exe       Enter the executable that is used for cppcheck.
                         (default=cppcheck)
    --clang-format=exe   Enter the executable that is used for clang-format.
                         (default=clang-format)
    --vera++=exe         Enter the executable that is used for vera++.
                         (default=vera++)

Assuming you are in source directory of NEST and you want to check all changed files between the commits 104d47c0 and d66e4465, execute the following line:

./extras/check_code_style.sh --git-start=104d47c0 --git-end=d66e4465

General Remarks and Resources

C++ Language Features

  1. Use only ISO C++ language features.
  2. Prefer ISO C++ library functions over their ISO C library equivalents.
  3. Prefer ISO C++ library containers (STL).
  4. Prefer C++ headers over their C equivalents.
  5. Don’t use printf and related functions.
  6. Use C++ style cast notation (see [1]).
  7. Use the const qualifier where appropriate. Use it consistently (see [5], chapter 6)!
  8. Use namespaces and exceptions.
  9. Try to avoid static class members which need a constructor (non POD).

Language of Comments and Identifiers

  1. All comments should be written in English.
  2. All identifiers, class and function names should be in English.

Debugging and Quality Control

Use the assert macro intensively to check program invariants [9]. Support for a C++ unit-testing framework for fine grain testing of the functionality is planned. Until then create unit-tests with the supplied SLI and Python unit-testing infrastructure.

Compiler

NEST compiles with any recent version of the GNU C/C++ Compiler gcc. Support and limitation for further compilers is described in the installation.

Online Reference Documents

  1. C++ Reference
  2. C++ Wikibooks

Books

We have found the following books to be useful.

  1. Stroustrup B (1997) The C++ Programming Language, 3rd Edition, Addison-Wesley
  2. Meyers S (1997) Effective C++, 2nd Edition, Addison Wesley
  3. Meyers S (1996) More Effective C++, Addison Wesley
  4. Coplien J O (1992) Advanced C++ programming styles and idioms, Addison-Wesley
  5. Eckle B (1995) Thinking in C++, Prentice Hall
  6. Plauger P J, Stepanov A, Lee M, and Musser D R (1998) The Standard Template Library, Comming June 1998, 1. Prentice Hall
  7. Plauger P J (1995) The (draft) Standard C++ Library, Prentice Hall
  8. Musser D R and Saini A (1996) STL Tutorial and Reference Guide, Addison-Wesley
  9. Kernighan B and Ritchie D (1988) The C Programming Language, 2nd Edition, Prentice Hall

Coding Style

In the following the coding style guidelines are explained by example and some parts are adopted from Google C++ Style Guide.

The #define Guard

All header files should have #define guards to prevent multiple inclusion. The format of the symbol name should be <FILE>_H. The file iaf_cond_alpha.h should have the following guard:

#ifndef IAF_COND_ALPHA_H
#define IAF_COND_ALPHA_H
...
#endif  // IAF_COND_ALPHA_H

Order of Includes

Use standard order for readability and to avoid hidden dependencies: Related header, C library, C++ library, other libraries’ .h, your project’s .h.

NEST’s Makefiles add all project specific include paths to the compile commands, thus the file iaf_cond_alpha.h should be included as: #include "iaf_cond_alpha.h"

In iaf_cond_alpha.cpp, whose main purpose is to implement iaf_cond_alpha.h, order your includes as follows:

  1. iaf_cond_alpha.h.
  2. C system files.
  3. C++ system files.
  4. Other libraries’ .h files.
  5. Your project’s .h files.

With the preferred ordering, if iaf_cond_alpha.h omits any necessary includes, the build of iaf_cond_alpha.cpp will break. Thus, this rule ensures that build breaks show up first for the people working on these files, not for innocent people in other packages.

Within each section the includes should be ordered alphabetically.

You should include all the headers that define the symbols you rely upon (except in cases of forward declaration). If you rely on symbols from bar.h, don’t count on the fact that you included foo.h which (currently) includes bar.h: include bar.h yourself, unless foo.h explicitly demonstrates its intent to provide you the symbols of bar.h. However, any includes present in the related header do not need to be included again in the related cpp (i.e., foo.cpp can rely on foo.h’s includes).

For example, the includes in <nestdir>/models/iaf_cond_alpha.cpp might look like this:

#include "iaf_cond_alpha.h"

#include <sys/types.h>
#include <unistd.h>
#include <hash_map>
#include <vector>

#include "config.h"
#include "foo.h"
#include "node.h"

Exception

Sometimes, system-specific code needs conditional includes. Such code can put conditional includes after other includes. Of course, keep your system-specific code small and localized. Example:

#include "iaf_cond_alpha.h"

#include "port.h"  // For LANG_CXX11.

#ifdef LANG_CXX11
#include <initializer_list>
#endif  // LANG_CXX11

Indentation, Formatting, and Naming

Files

Files are named in lower_case_under_lined notation. C/C++ Header files have the extension .h. C implementation files have the extension .c. C++ implementation files have the extension .cpp. The use of .cc is deprecated and is only left for compatibility.

All files in NEST start with a preamble, which contains the filename and the NEST copyright text (see example below).

Lines should not exceed 100 characters (clang-format). Files should not be to long (max. 2000 lines) (vera++:L006). No trailing whitespace (clang-format).

Folders

Use lower_case_under_lined notation for folder names.

Variables and Class Members

In general, use meaningful, non-abbreviated names or follow naming conventions from the neuroscience field, e.g. the membrane potential is V_m. Use the lower_case_under_lined notation. Private member variables should end with an underscore (name_).

Constants should be defined with enums and not with #define, and use the UPPER_CASE_UNDER_LINED notation:

enum StateVecElems
{
  V_M = 0,
  DG_EXC,
  G_EXC,
  DG_INH,
  G_INH,
  STATE_VEC_SIZE
};
Built-in Types

All code for the nest kernel should use the type aliases, defined in nest.h. Thus, use nest::float_t instead of float.

Functions and Class Methods

In general, use meaningful, non-abbreviated names or follow naming conventions from the neuroscience field, e.g. the membrane potential is V_m. Use the lower_case_under_lined notation.

There should be a line-break after the method’s return type (implementation only) (clang-format). Parameters of methods should either fit into one line or each parameter is on a separate line (clang-format).

inline void
nest::Stopwatch::print( const char* msg,
                        timeunit_t timeunit,
                        std::ostream& os ) const
{
  // code
}

Namespaces

Use lower_case_under_lined notation for namespaces. Do not use using namespace statements in header files (vera++:T018). The closing brace of a namespace should be followed by a comment containing the namespace statement. Do not indent the body of namespaces (clang-format).

namespace example
{
// code
} // namespace example

All symbols for the NEST kernel are declared in the namespace nest.

Structs and Classes

Use a struct only for passive objects that carry data; everything else is a class. Use CamelCase notation for naming classes, structs and enums, e.g. GenericConnBuilderFactory. Private, nested classes and structs end with an underscore (State_).

The access modifier (public, protected, private) in class definitions are not indented (clang-format).

Do not implement methods inside the class definition, but implement small inline methods after the class definition and other methods in the corresponding implementation file.

Template class declarations follow the same style as normal class declarations. This applies in particular to inline declarations. The keyword template followed by the list of template parameters appear on a separate line. The < and > in template expressions have one space after and before the sign, respectively, e.g. std::vector< int > (clang-format).

template< typename T >
class MyClass: public T
{
public:
  // code
private:
  // more code
};

Further Indentation and Formatting

Avoid committing indentation and formatting changes together with changes in logic. Always commit these changes separately.

As a general rule of thumb, always indent with two spaces (clang-format). Do not use TAB character in any source file (vera++:L002). Always use braces around blocks of code (vera++:T019). The braces of code blocks have their own line (clang-format).

Control structures (if, while, for, …) have a single space after the keyword (clang-format / vera++:T003, T008). The parenthesis around the tests have a space after the opening and before the closing parenthesis (clang-format). The case labels in switch statements are not indented (clang-format).

if ( x > 0 )
{
  // code
}
else
{
  // code
}

switch ( i )
{
case 0:
  // code
default:
  // code
}

Binary operators (+,-,*,||,&,…) are surrounded by one space, e.g. a + b (clang-format).

Unary operators have no space between operator and operand, e.g. -a (clang-format). Do not use the negation operator ! since it can easily be overseen. Instead use not, e.g. not vec.empty() (vera++:T012).

There is no space between a statement and its corresponding semicolon (clang-format):

return a + 3 ; // bad
return a + 3;  // good
Further checks performed by vera++:
  • F001 Source files should not use the ‘\r’ (CR) character
  • F002 File names should be well-formed
  • L001 No trailing whitespace (clang-format)
  • L003 no leading / ending empty lines
  • L005 not to many (> 2) consecutive empty lines
  • T001 One-line comments should not have forced continuation ( // ... \)
  • T002 Reserved names should not be used for preprocessor macros
  • T004 Some keywords should be immediately followed by a colon (clang-format)
  • T005 Keywords break and continue should be immediately followed by a semicolon (clang-format)
  • T006 Keywords return and throw should be immediately followed by a semicolon or a single space (clang-format)
  • T007 Semicolons should not be isolated by spaces or comments from the rest of the code (~ clang-format)
  • T010 Identifiers should not be composed of ‘l’ and ‘O’ characters only
  • T017 Unnamed namespaces are not allowed in header files
Further transformations performed by clang-format:
  • Align trailing comments
  • Always break before multi-line strings
  • Always break template declarations
  • Break constructor initializers before comma
  • Pointer alignment: Left
  • Space before assignment operators
  • Spaces before trailing comments: 1
  • Spaces in parentheses
  • Spaces in square brackets

Stopwatch example

For example, the stopwatch.h file could look like:

/*
 *  stopwatch.h
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  NEST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef STOPWATCH_H
#define STOPWATCH_H

#include <sys/time.h>

#include <iostream>
#include <cassert>

namespace nest
{
class Stopwatch
{
public:
  typedef size_t timestamp_t;
  typedef size_t timeunit_t;

  enum
  {
    MICROSEC = ( timeunit_t ) 1,
    MILLISEC = MICROSEC * 1000,
    SECONDS = MILLISEC * 1000,
    MINUTES = SECONDS * 60,
    HOURS = MINUTES * 60,
    DAYS = HOURS * 24
  };

  Stopwatch();

  void start();

  void stop();

  bool isRunning() const;

  double elapsed( timeunit_t timeunit = SECONDS ) const;

  timestamp_t elapsed_timestamp() const;

  void reset();

  void print( const char* msg = "",
              timeunit_t timeunit = SECONDS,
              std::ostream& os = std::cout ) const;

  friend std::ostream& operator<<( std::ostream& os,
                                   const Stopwatch& stopwatch );

private:
  timestamp_t begin_, end_;
  size_t prev_elapsed_;
  bool running_;

  static timestamp_t get_timestamp();
};

inline bool
Stopwatch::correct_timeunit( timeunit_t t )
{
  return t == MICROSEC || t == MILLISEC || t == SECONDS || t == MINUTES
         || t == HOURS || t == DAYS;
}

inline void
nest::Stopwatch::start()
{
  if ( not isRunning() )
  {
    prev_elapsed_ += end_ - begin_;  // store prev. time, if we resume
    end_ = begin_ = get_timestamp(); // invariant: end_ >= begin_
    running_ = true;                 // we start running
  }
}

inline void
nest::Stopwatch::stop()
{
  if ( isRunning() )
  {
    end_ = get_timestamp(); // invariant: end_ >= begin_
    running_ = false;       // we stopped running
  }
}

inline bool
nest::Stopwatch::isRunning() const
{
  return running_;
}

inline double
nest::Stopwatch::elapsed( timeunit_t timeunit ) const
{
  assert( correct_timeunit( timeunit ) );
  return 1.0 * elapsed_timestamp() / timeunit;
}

inline nest::Stopwatch::timestamp_t
nest::Stopwatch::elapsed_timestamp() const
{
  if ( isRunning() )
  {
    // get intermediate elapsed time; do not change end_, to be const
    return get_timestamp() - begin_ + prev_elapsed_;
  }
  else
  {
    // stopped before, get time of current measurment + last measurments
    return end_ - begin_ + prev_elapsed_;
  }
}

inline void
nest::Stopwatch::reset()
{
  begin_ = 0; // invariant: end_ >= begin_
  end_ = 0;
  prev_elapsed_ = 0; // erase all prev. measurments
  running_ = false;  // of course not running.
}

inline void
nest::Stopwatch::print( const char* msg,
                        timeunit_t timeunit,
                        std::ostream& os ) const
{
  assert( correct_timeunit( timeunit ) );
  double e = elapsed( timeunit );
  os << msg << e;
  switch ( timeunit )
  {
  case MICROSEC:
    os << " microsec.";
    break;
  case MILLISEC:
    os << " millisec.";
    break;
  case SECONDS:
    os << " sec.";
    break;
  case MINUTES:
    os << " min.";
    break;
  case HOURS:
    os << " h.";
    break;
  case DAYS:
    os << " days.";
    break;
  }
  os << std::endl;
}

inline nest::Stopwatch::timestamp_t
nest::Stopwatch::get_timestamp()
{
  // works with:
  // * hambach (Linux 2.6.32 x86_64)
  // * JuQueen (BG/Q)
  // * MacOS 10.9
  struct timeval now;
  gettimeofday( &now, ( struct timezone* ) 0 );
  return ( nest::Stopwatch::timestamp_t ) now.tv_usec
         + ( nest::Stopwatch::timestamp_t ) now.tv_sec
           * nest::Stopwatch::SECONDS;
}

} // namespace nest
#endif // STOPWATCH_H

And the corresponding stopwatch.cpp:

/*
 *  stopwatch.cpp
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  NEST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "stopwatch.h"

namespace nest
{
std::ostream& operator<<( std::ostream& os, const Stopwatch& stopwatch )
{
  stopwatch.print( "", Stopwatch::SECONDS, os );
  return os;
}
}


nest::Stopwatch::Stopwatch()
  : begin_( 0 )
  , end_( 0 )
  , prev_elapsed_( 0 )
  , running_( false )
{
}

clang-format configuration file

The file .clang-format is available in the top level source directory of NEST. It has to reside in the directory from which clang-format is run and does not have to be installed.

Vera++ profile

The Vera++ profile required for testing NEST is available as extras/vera++.profile. To make it available, copy this file with the new name nest to /usr/lib/vera++/profiles. The exact path might differ depending on how you installed Vera++. Please refer to the documentation of Vera++ in that case.