NuSpatial C++ Style Guide

This page defines a style guide to be followed in writing C++ code for NuSpatial.

It is based on:

Motivation

Coding style is important. A clean, consistent style leads to code that is more readable, debuggable, and maintainable. We strive to write elegant code that will not only perform its desired function today, but will also live on, to be re-used and improved by other developers for many years to come.

To this end, we prescribe (and proscribe) a variety of practices. Our goal is to encourage agile but reasoned development of code that can be easily understood by others.

These are guidelines, not rules. With very few exceptions, this document does not completely ban any particular C++ pattern or feature, but rather describes best practice, to be used in the large majority of cases. When deviating from the guidelines given here, just be sure to consider your options carefully, and to document your reasoning, in the code.

Above all else, be consistent. Follow this guide whenever possible, but if you are editing a package written by someone else, follow the existing stylistic conventions in that package (unless you are retrofitting the whole package to follow this guide, for which you deserve an award).

What about non-conforming code?

A lot of C++ code was written prior to the release of this style guide. Thus, the codebase contains a lot of code that doesn't conform to this guide. The following advice is intended for the developer working with non-conforming code:

  • All new code should conform to this guide.
  • Unless you have copious free time, don't undertake converting the existing codebase to conform to this guide.
  • If you are an author of non-conforming code, try to find time to update the code to conform.
  • If you are doing minor edits to non-conforming code, follow the existing stylistic conventions in that code (if any). Don't mix styles.
  • If you are doing major work to non-conforming code, take the opportunity to re-style it to conform to this guide.

Naming

The following shortcuts are used in this section to denote naming schemes:

  • CamelCased: The name starts with a capital letter, and has a capital letter for each new word, with no underscores.
  • camelCased : Like CamelCase, but with a lower-case first letter
  • under_scored: The name uses only lower-case letters, with words separated by underscores. (yes, I realize that under_scored should be underscored, because it's just one word).
  • ALL_CAPITALS: All capital letters, with words separated by underscores.

Files

All files are under_scored. Source files have the extension .cpp. Header files have the extension .h.

Libraries

Libraries, being files, are under_scored. Don't insert an underscore immediately after the lib prefix in the library name. E.g.:

lib_my_great_thing ## Bad
libmy_great_thing ## Good

Classes/Types

Class names (and other type names) are CamelCased E.g.:

class ExampleClass;

Exception: if the class name contains a short acronym, the acronym itself should be all capitals, e.g.:

class HokuyoURGLaser;

Name the class after what it is. If you can't think of what it is, perhaps you have not thought through the design well enough. Compound names of over three words are a clue that your design may be unnecessarily confusing.

Function/Methods

In general, function and class method names are camelCased, and arguments are under_scored, e.g.:

int exampleMethod(int example_arg);

Variables

In general, variable names are under_scored. Be reasonably descriptive and try not to be cryptic. Longer variable names don't take up more space in memory, I promise. Integral iterator variables can be very short, such as i, j, k. Be consistent in how you use iterators (e.g., i on the outer loop, j on the next inner loop).

STL iterator variables should indicate what they're iterating over, e.g.:

std::list<int> pid_list;
std::list<int>::iterator pid_it;

Alternatively, an STL iterator can indicate the type of element that it can point at, e.g.:

std::list<int> pid_list;
std::list<int>::iterator int_it;

Constants (deviates from ROS!)

Variables declared constexpr or const, and whose value is fixed for the duration of the program, are named with a leading "k" followed by mixed case. For example:

const int kDaysInAWeek = 7;

All such variables with static storage duration (i.e. statics and globals, see Storage Duration for details) should be named this way. This convention is optional for variables of other storage classes, e.g. automatic variables, otherwise the usual variable naming rules apply.

Member Variables

Variables that are members of a class (sometimes called fields) are under_scored, with a trailing underscore added.

E.g.:

int example_int_;

Global variables

Global variables should almost never be used (see below for more on this). When they are used, global variables are underscored with a leading g added.

E.g.,:

// I tried everything else, but I really need this global variable
int g_shutdown;

Namespaces

Namespace names are under_scored.

Formatting

Indent each block by 2 spaces. Never insert literal tab characters.

The contents of a namespace are not indented.

Braces, both open and close, go on their own lines (no "cuddled braces"). E.g.:

if(a < b)
{
  // do stuff
}
else
{
  // do other stuff
}

Braces can be omitted if the enclosed block is a single-line statement, e.g.:

if(a < b)
  x = 2*a;

Always include the braces if the enclosed block is more complex, e.g.:

if(a < b)
{
  for(int i=0; i<10; i++)
    PrintItem(i);
}

Line Length

Maximum line length is 120 characters.

#ifndef guards

All headers must be protected against multiple inclusion by #ifndef guards, e.g.:

#ifndef PACKAGE_PATH_FILE_H
#define PACKAGE_PATH_FILE_H
...
#endif

Optionally, #pragma once is acceptable.

This guard should begin immediately after the license statement, before any code, and should end at the end of the file.

Documentation

See Documentation Guide

Namespaces

Use of namespaces to scope your code is encouraged. Pick a descriptive name, based on the name of the package.

Never use a using-directive in header files. Doing so pollutes the namespace of all code that includes the header.

It is acceptable to use using-directives in a source file. But it is preferred to use using-declarations, which pull in only the names you intend to use.

E.g., instead of this:

using namespace std; // Bad, because it imports all names from std::

Do this:

using std::list;  // I want to refer to std::list as list
using std::vector;  // I want to refer to std::vector as vector

Inheritance

Inheritance is the appropriate way to define and implement a common interface. The base class defines the interface, and the subclasses implement it.

Inheritance can also be used to provide common code from a base class to subclasses. This use of inheritance is discouraged. In most cases, the "subclass" could instead contain an instance of the "base class" and achieve the same result with less potential for confusion.

When overriding a virtual method in a subclass, always declare it to be virtual, so that the reader knows what's going on.

Multiple inheritance

Multiple inheritance is strongly discouraged, as it can cause intolerable confusion.

Exceptions

Exceptions are the preferred error-reporting mechanism, as opposed to returning integer error codes.

Always document what exceptions can be thrown by your package, on each function / method.

Don't throw exceptions from destructors.

Don't throw exceptions from callbacks that you don't invoke directly.

If you choose in your package to use error codes instead of exceptions, use only error codes. Be consistent.

Enumerations

Namespaceify your enums, e.g.:

namespace Choices
{
  enum Choice
  {
     Choice1,
     Choice2,
     Choice3
  };
}
typedef Choices::Choice Choice;

This prevents enums from polluting the namespace they're inside. Individual items within the enum are referenced by: Choices::Choice1, but the typedef still allows declaration of the Choice enum without the namespace.

Globals

Globals, both variables and functions, are discouraged. They pollute the namespace and make code less reusable.

Global variables, in particular, are strongly discouraged. They prevent multiple instantiations of a piece of code and make multi-threaded programming a nightmare.

Most variables and functions should be declared inside classes. The remainder should be declared inside namespaces.

Exception: a file may contain a main() function and a handful of small helper functions that are global. But keep in mind that one day those helper function may become useful to someone else.

Static Class Variables

Static class variables are discouraged. They prevent multiple instantiations of a piece of code and make multi-threaded programming a nightmare.

Calling exit()

Only call exit() at a well-defined exit point for the application.

Never call exit() in a library.

Assertions

Use assertions to check preconditions, data structure integrity, and the return value from a memory allocator. Assertions are better than writing conditional statements that will rarely, if ever, be exercised.

Testing

See Testing Guide

results matching ""

    No results matching ""