Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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.

All code should respect the C++ Core Guidelines, unless doing so would contradict this document.

Contents

Table of Contents
maxLevel3

...

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

Variables and constants

...

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

Global variables (which should be avoided unless they are absolutely necessary) and module-local variables should be prefixed with g_, e.g. g_foo.

...

All preprocessor definitions are CAPITALS_WITH_UNDERSCORES. Usage of preprocessor definitions should be restricted to build configuration variables and debug macros. Never use those to define constants; use constexpr or const instead.

Namespaces

Namespace names are under_scored.

...

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

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

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

...

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

...

Blocks of code must be indented with 4 spaces. Tabs are not allowed Usage of tabs anywhere in the source code at allis not permitted.

The following blocks must not be indented:

...

Code Block
languagecpp
void foo()
{
    // This is a single-line comment. Observe the space between the slash and the first letter.
    int a = 123;  // At least onetwo spacespaces isare required on eitherthe left side of the '//' sequence, unless the comment is located on a dedicated line.

    /*/ Suppose that this is a *very Justlong a multicomment that won't fit on one line.
    // It is preferred to use multiple single-line commentcomments here.rather that a single multi-line comment */block.
    bar(a);
}

Declarations and definitions

Never declare more than one variable (or field) on the same line.

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

Bit field declarations must surround the colon with spacesshould put at least one space on the right of the colon: unsigned field: 8.

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

Very simple member functions (e.g. getters), trivial constructors, or trivial destructors can be defined in on a single line.

Code Block
languagecpp
intstd::int32_t foo = 0;             // OK
const intstd::int32_t* a = nullptr;  // OK
intstd::int32_t& b = foo;            // OK
intstd::int32_t volatile *c, d;      // Wrong

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

    intstd::int32_t getField() const { return field; }
};

...

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, brackets, and brackets braces should not have spaces on the inside (does not apply to curly braces): if (a) {}, foo[i]();.

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

...

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

...

  • Copyright;
  • Type of the license (but not the full text of it, unless the license requires otherwise);
  • Information about the author.
Code Block
languagecpp
/*/  * Copyright (c) 20152019 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. An uninitialized variable is to be considered a mistake, even if its value is to be immediately overwritten.

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 (e.g., by scoping the code explicitly with braces).

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.

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

    {
        unsignedstd::size_t 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)
    {
        unsignedstd::size_t 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;
            }
        }
    }
}

...

It is preferable to avoid function-style cast, e.g. FooType(x), unless doing so would hurt readability and conciseness. This rule should not be enforced automatically.

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;.

...

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.

Code Block
languagecpp
Eigen::Vector3dVector<3> predictMeasurement() const
{
    const doubleScalar qw = x_[0];  // Note const
    const doubleScalar qx = x_[1];
    const doubleScalar qy = x_[2];
    const doubleScalar 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)));
}

...

Code Block
languagecpp
namespace foo
{

// A bunch of stuff here

}  // namespace foo

// Some other stuff goes here

...

In C++, it is explicitly prohibited to use the preprocessor for the purpose of defining a regular 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.

...

C++ projects should be written in the standard C++14 17 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. Exception applies to #pragma once.

...

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 equivalently strict or stricter settings.

Optimization

Aggressive compiler optimizations need to be avoided.

...

Info
titleTODO

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

Code Block
languagecpp
/*
 * 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
{


using Scalar = double;

class [[nodiscard]] Point
{
    Scalar x_ = 0.0;       // Note default initialization
    Scalar y_ = 0.0;       // ditto

public:
    Point() = default;

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

    [[nodiscard]] Scalar computeDistanceTo(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);
    }
};

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

} // namespace foo