Page tree
Skip to end of metadata
Go to start of metadata

This document defines the C/C++ style adopted by Zubax Robotics.

These guidelines are based on MISRA C++, High Integrity C++ Coding Standard, and the ROS C++ Style Guide. The reader is assumed to be familiar with these.

Contents

Naming

The general rule is to be descriptive. Don't hesitate to use longer names as they tend to improve clarity.

Files

File names are under_scored. The recommended file extensions are as follows:

  • C++ source files are .cpp.
  • C++ header files are .hpp.
  • C source files are .c.
  • C header files are .h.

Types

Type names are CamelCased. If a type name contains an acronym, the acronym itself should be capitalized, e.g. IOManager, beginUSBTransfer(). Same rule applies to typedef and template parameters.

In C, structs, unions, and enumerations should be referred by name rather than by tag. Suffix _t is not allowed. See the example:

// C code
typedef struct
{
    int bar;
} FooState;                // OK

typedef enum
{
    FooErrInvalidMoonPhase,
    FooErrUnmetExpectations
} FooErrors;               // OK

struct FooData_t           // Wrong
{
    double baz;
};

Functions

Function names and method names are camelCased, the first character must be lowercase.

In C++, function declarations should not use an explicit void in an empty argument list: int getFoo() { return 42; }

Variables and constants

Variables and function arguments are under_scored. Never use the hungarian notation.

Function arguments that are supposed to be modified by the function should be prefixed with out_ or inout_, depending on whether or not the value will be used by the function before being overwritten.

Constants and enumeration members are CamelCased. If a constant or enumeration name contains an acronym, the acronym itself should be capitalized, e.g. USBVendorID.

Global variables and module-local variables should be prefixed with g_, e.g. g_foo.

Member variables should be suffixed with an underscore, e.g. balalaika_.

Preprocessor definitions

All preprocessor definitions are CAPITALS_WITH_UNDERSCORES.

Namespaces

Namespace names are under_scored.

Scoping

In C++, all declarations except the main() function should reside within a namespace.

In C, all public entities should be prefixed with the name of the component they belong to (e.g. library name) in order to avoid name clashing.

// C code
const int FooConstant = 42;       // OK, prefixed

int fooComputeTheGreatAnswer()    // OK, prefixed
{
    return FooConstant;
}

static void doNothing()           // OK, not prefixed because not public
{
    puts("Doing nothing...");
    puts("Done.");
}

Templates

Never use the keyword class in a template argument list, use typename instead. The special cases where the keyword typename was unacceptable have been removed in C++17.

template <typename A, typename B>
inline decltype(std::pow(A(), B())) Power(A a, B b)
{
    return std::pow(a, b);
}

Formatting

Indentation

Blocks of code must be indented with 4 spaces. Tabs are not allowed in the source code at all.

The following blocks must not be indented:

  • Contents of a namespace;
  • Case blocks in a switch statement.

Comments are always placed on the same indentation level as the code block that contains them. There should be at least one space at the beginning of each comment line, as shown below:

void foo()
{
    // This is a single-line comment. Observe the space between the slash and the first letter.
    int a = 123;  // At least one space is required on either side of the '//' sequence, unless the comment is located on a dedicated line.

    /*
     * Just a multi-line comment here.
     */
    bar(a);
}

Declarations and definitions

Never declare more than one variable on the same line.

Asterisk and ampersand characters must be placed with the type name, not with the variable name: int* ptr.

Bit field declarations must surround the colon with spaces: int field : 8.

CV qualifiers must always be placed before the type name: const FooBar& foobar.

Very simple member functions (e.g. getters) can be defined in a single line.

int foo = 0;             // OK
const int* a = nullptr;  // OK
int& b = foo;            // OK
int volatile *c, d;      // Wrong

class Field
{
    const int field : 8;
public:
    Field(int value) : field(value) { }

    int getField() const { return field; }
};

Braces, brackets, parentheses

Braces should be placed on their own lines, except for single-line member functions. Never omit braces, not even for single-line statements, such as trivial if. Case blocks in a switch statement must be enclosed in braces as well.

Parentheses and brackets should not have spaces on the inside: if (a) { }, foo[i]();.

A space should always be inserted between the template keyword and the following angle brace: template <typename>.

Operators

The following binary and ternary operators should be surrounded with spaces:

  • Arithmetic a + b
  • Logical a || b
  • Bitwise a & b
  • Comparison a != b
  • Compound a &= b
  • Ternary a ? b : c

Operator comma needs only one space on the right side of it: a, b.

Unary operators and all other operators should be adjacent to the operand, no space needed: a[i]->b(!value, *ptr).

Overloaded operator definitions should not put spaces around the operator symbol: bool operator==(const Foo&).

Classes, structs, unions

Visibility

Members should be grouped by visibility in the following order:

  • Private;
  • Protected;
  • Public.

Constructor initialization list

Each member of an initialization list should be placed on a separate line. However, if the initialization list consists of one element, it can be kept on the same line. Colons in class definitions should be surrounded with spaces.

struct A
{
    int foo = 0;

protected:
    int bar = 1;

public:
    A(int arg_foo) : foo(arg_foo) { }

    A(int arg_foo, int arg_bar) :
        foo(arg_foo),
        bar(arg_bar)
    { }
};

Note that some older sources use a different convention that is now deprecated, due to the fact that it could not be enforced with autoformatters.

Lines

No line of code should be longer than 120 characters.

Trailing whitespaces are not allowed; make sure to configure your text editor to automatically trim trailing whitespaces on save in the entire file.

Every text file should contain exactly one empty line at the end.

Allowed end-of-line character sequence is Unix-style (\n, LF). Windows or Mac line endings are not allowed.

File header

File headers should include at least the following information:

  • Copyright;
  • Type of the license (but not the full text of it, unless the license requires otherwise);
  • Information about the author.
/*
 * Copyright (c) 2015 Zubax Robotics, zubax.com
 * Distributed under the MIT License, available in the file LICENSE.
 * Author: Pavel Kirienko <pavel.kirienko@zubax.com>
 */

Coding

Variables

All variables must be default-initialized: double foo = 0.0.

Variables that aren't supposed to change must be declared const or constexpr. Use constexpr where possible.

Variables must be defined immediately before use. Try to limit the scope of variables when possible.

It is explicitly disallowed to define all variables at the beginning of the function, the way often used with early versions of the C standard.

template <typename InputIter>
void packSquareMatrixImpl(const InputIter src_row_major)
{
    const unsigned Width = CompileTimeIntSqrt<MaxSize>::Result;           // OK - const
    bool all_nans = true;                                                 // OK - inited
    bool scalar_matrix = true;                                            // ditto
    bool diagonal_matrix = true;                                          // ditto

    {
        unsigned index = 0;                                               // OK - scoped
        for (InputIter it = src_row_major; index < MaxSize; ++it, ++index)
        {
            const bool on_diagonal = (index / Width) == (index % Width);  // OK - const
            const bool nan = isNaN(*it);                                  // OK - const
            if (!nan)
            {
                all_nans = false;
            }
            if (!on_diagonal && !isCloseToZero(*it))
            {
                scalar_matrix = false;
                diagonal_matrix = false;
                break;
            }
            if (on_diagonal && !areClose(*it, *src_row_major))
            {
                scalar_matrix = false;
            }
        }
    }

    this->clear();

    if (!all_nans)
    {
        unsigned index = 0;                                              // OK - scoped
        for (InputIter it = src_row_major; index < MaxSize; ++it, ++index)
        {
            const bool on_diagonal = (index / Width) == (index % Width); // OK - const
            if (diagonal_matrix && !on_diagonal)
            {
                continue;
            }
            this->push_back(ValueType(*it));
            if (scalar_matrix)
            {
                break;
            }
        }
    }
}

Methods

All methods must be declared const whenever possible.

Type casting

Use of the C-style cast in C++ code is strictly prohibited, with one exception of the cast to void, reviewed below. Use static_cast, reinterpret_cast, const_cast, and dynamic_cast instead.

It is preferable to avoid function-style cast, e.g. FooType(x).

Cast to void can be used to explicitly mark a variable unused, suppressing the warnings from the compiler or the static analyzer: (void) unused_variable;.

Scoping

The statement using namespace should not be used, except in a very local scope (see an example below). Usage of this statement in a global scope or in a namespace scope is strictly prohibited.

Eigen::Vector3d predictMeasurement() const
{
    const double qw = x_[0];  // Note const
    const double qx = x_[1];
    const double qy = x_[2];
    const double qz = x_[3];

    using namespace mathematica;  // OK - pulling the definitions of List() and Power()
    return List(List(-2 * qw * qy + 2 * qx * qz),
                List(2 * qw * qx + 2 * qy * qz),
                List(Power(qw, 2) - Power(qx, 2) - Power(qy, 2) + Power(qz, 2)));
}

It is encouraged to provide comments at the ends of very long scopes (such as namespaces) that indicate what scope the closing brace belongs to. For example:

namespace foo
{

// A bunch of stuff here

} // namespace foo

// Some other stuff goes here

Other

In C++ code, usage of the NULL macro or the literal zero (0) where a null pointer is expected is prohibited. Instead, use the nullptr literal.

Preprocessor

In C++, only the following uses are allowed for the preprocessor:

  • Header file inclusion;
  • Include quards;
  • Conditional compilation.

Any other use of the preprocessor may be allowed only if a detailed explanation and full justification of it is provided near the point of the definition.

In C++, it is explicitly prohibited to use the preprocessor for the purpose of defining a constant or a general-purpose macro function; no exceptions to this rule are allowed.

As for include guards, #pragma once should be preferred over the explicit set of #ifndef ... #define ... #endif, unless the code must be highly portable across different compilers that may not support this feature.

For conditional compilation, prefer #if, not #ifdef. Check for the existence of the variable before use is encouraged, as shown below.

#ifndef UAVCAN_EXCEPTIONS
# error UAVCAN_EXCEPTIONS
#endif

#if UAVCAN_EXCEPTIONS
# include <stdexcept>
#endif

Logging

Messages should be capitalized (do not force lowercase). Every log message should be prefixed with the name of the component where the message is emitted from, separated from the message itself with a colon and a space.

lowsyslog("TimeSyncSlave: Init failure: %i, will retry\n", res);

Documentation

All public entities (functions, variables, constants, etc), especially those that are parts of interfaces, should be documented in Doxygen.

Building

Language standards

C++ projects should be written in the standard C++14 or a newer version of the language.

C code should be written in the standard C99. Normally, C should be always avoided in favor of C++.

It is not permitted to use any compiler-specific features, unless they are protected by appropriate conditional compilation statements.

Compilation warnings

All release builds must be warning-free, unless the warnings originate from third-party components that cannot be altered.

For GCC-like compilers, the following flags are mandatory: -Wall -Wextra -Werror. Use of the following flags is encouraged: -pedantic -Wundef -Wdouble-promotion -Wswitch-enum -Wconversion -Wfloat-equal.

Other compilers should be used with equivalent settings.

Optimization

Aggressive compiler optimizations need to be avoided.

For GCC, optimization levels other than -O1 should not be used. -O2 or -Os should be used only in situations where the performance or memory footprint of the resulting executables are critical for the target application. -O3 is explicitly prohibited, as well as aggressive options such as -ffast-math -fno-math-errno -funsafe-math-optimizations -ffinite-math-only -fno-rounding-math -fno-signaling-nans -fcx-limited-range.

Strict aliasing and strict overflow options should never be used.

Code safety assurance

TODO

Set up the list of mandatory rules from MISRA C++ and HIC++; for each rule provide a list of tools that can enforce it.

Example

/*
 * Copyright (c) 2014 Zubax Robotics, zubax.com
 * Distributed under the MIT License, available in the file LICENSE.
 * Author: Pavel Kirienko <pavel.kirienko@zubax.com>
 */

#pragma once

#include <cmath>

namespace foo
{

class Point
{
    double x_ = 0.0;       // Note default initialization
    double y_ = 0.0;       // ditto

public:
    Point() { }

    Point(double arg_x, double arg_y) :
        x_(arg_x),
        y_(arg_y)
    { }

    double distance(const Point& other) const
    {
        const auto dx = x_ - other.x_;    // Note const
        const auto dy = y_ - other.y_;    // ditto
        return std::sqrt(dx * dx + dy * dy);
    }

    int compareX(const Point& other) const
    {
        if (x_ < other.x_)
        {                                  // Note braces
            return -1;
        }
        else if (x_ > other.x_)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }
};

inline void foo(int& inout_bar)
{
    switch (inout_bar)
    {
    case 0:
    {
        ++inout_bar;
        break;
    }
    case 1:
    {
        --inout_bar;
        // FALLTHROUGH
    }
    default:
    {
        inout_bar += inout_bar;
        break;
    }
    }
}

} // namespace foo