We use C++14, but currently consider C++17 too immature for usage.
This documents contains guidelines regarding:
- File structure
- Comments and documentation
- Naming
- Formatting
- Typing
- Namespaces
- Structs
- Classes
- Error handling
- Memory handling
- Resource handling
- Other
-
Header files:
.hpp -
Source files:
.cpp -
Use
#pragma oncein headers. -
Split class definition across a header and source file by default.
-
Include header definitions in the same file.
-
Order of includes
- Internal library headers
- Other Fetch library headers
- Vendor library headers
- System/standard library headers
-
Class access specifiers in this order
publicprotectedprivate
- Provide API documentation with your code.
- Include a comment summary at the top of all files.
/**
* Approximation of the exponential function.
*
* We exploit the IEEE-754 standard to efficiently create a linear
* interpolation between points of the form 2^n where n is an integer.
*
* You can find the full details in
* [1] "A fast, compact approximation of the exponential function",
* Nicol N. Schraudolph, 1999 Neural computation 11, 853-862.
*
* @param x the argument that is used in the exponent 2^(x / M_LN2).
* @returns The exponential of x
*/
double Exp(double const &x)
{
// We use the constants explained in [1] and additional make e the
// base rather than using 2.
static double const a = double(1ull << 20) / M_LN2;
static double const b = ((1ull << (10)) - 1) * double(1ull << 20) - 60801;
double in = x * a + b;
// Move the computed bits into the exponent with spilling into the
// Mantissa. The spilling creates the linear interpolation, while
// the exponent creates exact results whenever the argument is an integer.
union
{
double d;
uint32_t i[2];
} conv;
conv.i[1] = static_cast<uint32_t>(in);
conv.i[0] = 0;
return conv.d;
}
- Comment the non-obvious, not the obvious.
// Turns out sorting this list before processing it is faster due to branch
// prediction, so don't remove this without checking the profile:
std::sort(users.begin(), users.end(), UserSortPredicate);
for (User const &user : users)
{
if (user.ready)
{
// Do something...
}
}- If you do not understand some code, contact the original author. If they have left the company ask the most competent person. After working out what a piece of a non-obvious code is doing, remember to document it.
- Use meaningful names, even in tests.
- For struct and class names, use
PascalCase. - Private and protected class member variables end with an
_. - Variable names are
snake_case_with trailing underscore, except in structs where it issnake_casewithout underscore. - For templates, use all uppercase with underscores to separate words. Long and explicit names are preferred to short and obscure names, e.g.
template <typename ARRAY_TYPE>is preferable totemplate <typename A>);. Short names are acceptable where the type is obvious and reused many times in complex equations. - For enums use
UPPER_CASE. - For macros use
UPPER_CASE.
- Make use of the
apply_stylescript provided for formatting. - Lines should be no longer than 100 characters.
- Use 2 spaces to indent, not tabs.
- Curly brackets
{...}should be on their own line. - There should be a space after conditionals, i.e.
if (test) - No spaces inside
(parenthesis)or<template>unless it improves readability. - No spaces around arrow:
this->fnc();. - No trailing whitespace.
- Align attributes by group in classes, and initialisers.
- Variables should be initialised with
=if primitive, or braced initialisation with parentheses if required. - Initialiser lists should be initialised with braces by default.
- Use SQL style initialiser for lists and inheritance lists.
Foo::Foo()
: attribute1_{value1}
, attribute2_{value2}
{}-
Use STL types wherever possible.
-
Prefer
(u)int(16/32/64)_tandstd::size_t. -
Use
static_cast,reinterpret_cast,dynamic_cast, etc., rather than C-style casts. -
Avoid
autoexcept when the return type is clear. -
Avoid use of RTTI.
-
usingis preferable totypedeffor type alias definition. -
Make
usingprivate unless you intend to expose it. -
namespace {.<template T>only allowed iftypedef/usingindicates the type. -
If a value is
const, declare it so. -
Use
int const &ioverconst int &i. -
Prefer
enum classover regularenum.
- Do not do
using namespace fooornamespace baz = ::foo::bar::bazin.hppfiles. - Do not do
using namespace fooin the topfetchnamespace, as it pollutes all of it. Instead, do the following:
namespace fetch {
namespace details {
struct FetchImpl
{
/* ... */
};
void HelperFunction(FetchImpl &s)
{
/* ... */
}
} // namespace details
class MyFetchClass
{
public:
void DoSomeThing()
{
// This improves readability
using namespace details;
FetchImpl f;
HelperFunction(f);
}
// ...
};
} // namespace fetchUse structs for Plain Old Data (POD) and, in some cases, template meta-programming. Use classes for everything else.
struct Person
{
std::string name;
int age;
// ...
};- Initialise class members in the following order of preference:
- Inline initialisation. This ensures, for instance, that the default constructor does not produce uninitialised memory.
- Constructor initialiser list. Member variables must be listed in the order they appear.
- Constructor body.
In most cases, a combination of the above will be used. For example:
class MyClass
{
public:
// Inline initialisation
MyClass() = default;
// Initialisation list for one attribute, constructor body for another
explicit MyClass(std::size_t n)
: size_(n)
{
doubled_ = n * 2;
}
// ...
private:
std::size_t size_ = 0;
std::size_t doubled_ = 0;
};- Mark all constructors and assignment operators explicitly, even those which are implicit, to ensure there is no confusion about the intended behaviour of the class.
- Mark unneeded default implementations as deleted. C++ infers whether to create implicit constructors based on whether there are user defined ones. This means that when someone declares a constructor, C++ may remove the default constructor.
{
public:
Transaction() = default;
Transaction(Transaction const &) = default;
Transaction(Transaction &&) = deleted;
~Transaction() = default;
Transaction operator(Transaction const &) = default;
Transaction operator(Transaction &&) = deleted;
}- Mark a class
finalif it is not meant to be subclassed. The same counts for final implementations of virtual functions.
-
Always check error codes and return codes.
-
Use exceptions only for exceptional circumstances.
-
Handle normal errors gracefully.
-
Do not rely on exceptions to avoid taking responsibility.
-
Do not throw exceptions in destructors, in order to prevent calls to
std::terminateduring stack unwinding (as per the C++ standard definition).
- Use references where the value cannot be null. Use pointers where it can.
- Make sure no uninitialised memory is accessed or created.
- Always prefer passing by reference or
constreference where possible. - Prefer smart pointers (
std::shared_ptr,std::unique_ptr, etc) over raw pointers. - Use
make_sharedandmake_uniqueto wrap objects in smart pointers. - Prefer the type-safe
nullptrover the weakly-typed literal0and theNULLmacro. - Stick to RAII principle and automatic memory management - destructors are ideally empty.
- When working with array object types, prefer existing structures that already implement memory management:
- From the standard library, such as
std::vectorandstd::string. SharedArrayandArrayclasses fromfetch::memorynamespace.- Classes from
fetch::byte_arraynamespace, such asBasicByteArray.
- From the standard library, such as
- Use the locking guard
FETCH_LOCKwhenever possible, and only those provided in C++stdnamespace (such asstd::unique_lockandstd::lock_guard) when necessary. - Prefer implementations of mutex in the
fetch::mutexnamespace (which conform to astdlocking API contract) over a plainstd::mutex. These arefetch::mutex::DebugMutexandfetch::mutex::ProductionMutex.
- Don't use
#define. If you ignore this guidance, choose aFETCH_STARTING_REALLY_LONG_NAME_THAT_CANNOT_POSSIBLY_CLASH_WITH_ANYTHING_ELSEand undefine it as soon as you are done with it. - Use the following keywords when appropriate:
explicit.override.virtual.final.noexcept.
- Pre increment:
++ias it is more performant for non primitive types. - Lambda captures must be explicit reference or copy.
- TODOs should refer to the author in the style
TODO(HUT) :. - Avoid leaving
TODOsin code. - Use templates judiciously and defined to library code where only a small number of developers need to understand them.