Style Guide

December 10, 2015 · View on GitHub

We generally follow the STL/Boost style conventions to avoid style clashing with core libraries. That means

  • Use snake_case classes, types, functions, and variables.
  • Use _name or name_ for private variables in classes/structs.
  • Use (4) spaces rather than tabs.

Other style guidelines

  • Follow the style of the project you’re in.
  • Enforce with cpplint.py (as done in cpp-project-template).
  • Use Doxygen style annotations for documenting source code, preferably /** … */
  • Use forward-declarations, especially with anything from windows.h
  • Use {} for object initialization, as in std::string s {“foo”};. It’s easier to differentiate from function calls and function definition.
  • Use override (and not virtual) when overriding methods through inheritance. Obviously the original method still needs virtual (and not override).
  • Use #pragma once at the beginning of header files. It’s supported by every modern compiler, and is simpler to use than #ifdef guards.
  • Avoid casts. If you have to cast, use C++ casts (static_cast, dynamic_cast, const_cast, reinterpret_cast, preferred in that order) and cry a little bit inside.

Header Files

Header file inclusion1:

  • Every cpp file includes its own header file first.
  • A header file must include all the header files necessary to parse it.
  • A header file should have the bare minimum number of header files necessary to parse it. Prefer forward-declarations when possible, i.e. whenever you only need pointers/references to a class outside the std namespace2.

Header file inclusion is done as a pre-processing step that literally in-lines the text from the header file into the text buffer the compiler is parsing. The only exception to this is the use of #pragma once, which skips including the file if it's previously been included.

Namespaces

Never put using namespace x in a header file; doing so causes the effect to be applied to every header file that's included after it is included, and to every source file that includes it. If you want a short-hand in a header file, use aliases (i.e. namespace x = long_namespace_x) inside of a namespace declared in that header file, as in

namespace foo {
    namespace x = long_namespace_x;
    x::widget process_widget(x::widget);
}

The effect of the example above is to create a namespace x within the namespace foo, so that you could reference long_namespace_x::widget via foo::x::widget. The short-hand x::widget works because name lookup will search for matches that share a parent namespace first.

Avoid using namespace aliases in header files outside of a namespace, as this can have unbounded effects on other header files. Be cautious even doing so in a namespace, as namespaces can be shared across multiple header files.

Prefer namespace aliasing in source files within the namespace as well.

Exceptions vs Error-Handling

The debate of exceptions vs error-handling is long running. They can both fulfill the same function: returning whether an error occured and the context in which it happened. The trade-offs are

  • Errors must be specifically handled. If the failure needs to be handled further up, error-handling code gets verbose.
  • Exceptions can be more expensive.

Read the Boost community's answer. In practice, use error codes or optional return values when failure reasons are obvious or in performance-critical code. Otherwise use exceptions.

Ensure exceptions don't propagate outside of a C interface; they're meant for language interop (e.g. Ruby), and you want to avoid the caller terminating if an exception is thrown within a library it's using. Provide other error handling instead.

If error-handling becomes commonly used in places we want to return more context (i.e. a string), we should add a general error context class to Leatherman.

TODO: Still under development.