Skip to content

Code Style Guidelines

Phil Miller edited this page Aug 13, 2020 · 2 revisions

Policies for C++ Code

Use explicit for single-argument constructors

Unless a constructor is intended to have implicit conversion semantics, use explicit for single argument constructors for all classes.

Use cstdint typedefs for integers

  • Unless you are using a normal 32-bit int, use the typedefs in cstdint for all other combinations. Using unsigned, long long, long, and so on can vary across platforms are in general and less descriptive when reading the code. Thus, use the following except for the special case of a normal int:
Signed Unsigned
int8_t uint8_t
int16_t uint16_t
int32_t uint32_t
int64_t uint64_t
  • For integers that must hold a pointer, always use uintptr_t or intptr_t.

Initialize all variables in classes (esp. primitive types)

Even if your constructors sets them, always initialize them. This way if another constructor is added we never have to worry about uninitialized variables.

struct A {

  A() : a(10), b(4.f) { }

  int a = 0;
  float b = 0;
};

Always use default for constructors (and the like) if needed and appropriate

struct A {
 A() { } // don't do this

 A() = default; // do this!
 A(A const&) = default;
 A(A&&) = default;
 A& operator=(A const&) = default;
};

When someone reading the code sees default they know exactly what it means rather than an empty constructor. It reduces the overhead of reading code and chance of mistakes. If the default behavior is appropriate, don't include them at all.

Please read the rule of 3/5/0

Formatting long statements/expressions

  1. Try to keep lines under 80 cols in length with a few exceptions when it makes sense.
  2. Wrap parens and templates on the next line to keep them aligned
void test() {
  auto result = function(
    param1, param2, param3, this->getIndex(), param4, param5, param6,
    param7, param8
  );
  return result + 1;
}

template <
  typename A, typename B = typename std::enable_if_t<std::is_same<A, long>::value>,
  typename C
>
struct Test { };

Type aliases: typedefs vs. using

  • Avoid using typedef, preferring use of using. The using construct makes the order more sane to read. Make type aliases whenever it seems useful. If you think a type might change later or is more useable, create a type alias.
using NodeType = uint16_t;

struct A {
  using ActionListType = std::list<std::function<void()>>;

private:
  ActionListType actions_;
};
  • Avoid usage of using namespace X anywhere in the code (except maybe in tests)
  • Import names you need precisely within a scope where required
namespace X {
using NodeType = int16_t;
} /* end namespace X */

void myFunction(int x) {
  if (x == 100) {
    using X::NodeType;
    NodeType y = static_cast<NodeType>(x);
  }
}

Namespaces

  • Do not indent for new namespaces, and keep them on one line
namespace vt { namespace group {

struct MyGoup { };

}} /* end namespace vt::group */

Enums

  • Use an enum struct when possible. Degrading automatically (happens with non-strongly typed enums) can introduce bugs and provides less type checking.
enum struct CallbackEnum : int8_t {
  ConstantOne = 0,
  ConstantTwo = 1
};
  • To get the underlying enum type for a strongly typed enum call std::underlying_type<T>

Single-line statements (if, while, etc.)

For single-line blocks use curly braces.

if (done) {
  return 10;
}
while (not done) {
  vt::runScheduler();
}

Some code to show preferred style by example

/**
 * \struct OtherClass
 *
 * \brief See preferred spacing on pure virtual method.
 */
struct OtherClass {

  /**
   * \brief A pure virtual method
   *
   * \param[in] num_times number of times the scheduler should be called
   */
  virtual void myTestMethod(int num_times) = 0;
};

/**
 * \struct MyClassName
 *
 * \brief Prefer "struct" over "class", reducing boilerplate
 *
 * Using "struct" gives you a default public visibility for the inheritance
 * and class members
 */
template <typename T, typename U = void>
struct MyClassName : OtherClass {

  using MyIntType = int32_t;
  using IteratorType = typename std::vector<MyIntType>::const_reverse_iterator;

  // Use defaults when possible and needed
  MyClassName() = default;
  MyClassName(MyClassName&&) = default;
  MyClassName(MyClassName const&) = default;

  // Often your destructor should be virtual
  virtual ~MyClassName() = default;

  // if it is above 80 cols, wrap to next line with regular indenting
  static inline typename Abc<T,X>::type mySimpleMethod(
    int a, int b, int c, int d
  );

  // Use the override keyword, it can only help you
  void myTestMethod(int a) override {
    // Initialize stack variables as much as possible (and static ones when possible)
    int a = 0, tmp = 0;
    int my_local_count = 0;
    std::vector<int> my_vec = {};

    /*
     * Consider using "and", "or", "not" instead of "&&", "||", "!". 
     * Often it's more readable. It's fully C++ standard compliant.
     * Not required, just a personal style opinion.
     */
    if (my_local_count != 0 and a == 0) {
      a = 10;
    }

    // Braces instead of semicolon when empty
    // Prefer "true" instead of "1"
    while (true) { }

    // Prefer spacing here
    for (int i = 0; i < 10; i++) {

    }

    for (auto&& elm : my_vec) { }
  }

  // if function returns bool, avoid type aliases
  bool valid() const;

private:
  // Private members have trailing underscore
  int my_data_ = 0;
  bool is_coll_ = true; /**< for booleans prefix with "is"/"has"/etc */
};

/**
 * \struct ManyTemplateParams
 *
 * \brief A example class with many template parameters
 *
 * Wrap typename args when they get long, start ">" on next line, indented as
 * so. Use "typename" unless you have to use "class": exception being template
 * templates. Space goes here: "template <". Ellipses on on LHS for parameter
 * packs.
 */
template <
  typename CollectionT, typename IndexT, typename TupleT, typename RetT,
  typename... Args
>
struct ManyTemplateParams {
  // Wrap function args when they get long, indented as so
  RetT operator()(
    CollectionT const& col, IndexT const& idx, TupleT&& tup,
    std::tuple<Args...> tup, std::unique_ptr<intt64_t> ptr
  ) {
    return {};
  }
};

Doxygen

  • Refer to doxygen style guidelines
  • Personally I use doxymacs in emacs for generating doxygen statements automatically for functions/members
    • To generate for a function/method: doxymacs-insert-function-comment
    • To generate for a member: doxymacs-insert-member-comment
    • For a general multiline comment: doxymacs-insert-blank-multiline-comment
    • I (Jonathan) have modified the plugin to follow the preferred doxygen style guidelines. If you want to use it, contact me and I will give you the elisp file.