General Guidelines

Common C++ Naming Conventions

  • Types start with uppercase: MyClass
  • Functions and variables start with lowercase: myMethod
  • Constants are all upper case: const double PI=3.14159265358979323;

Position of Operators

  • In general, place \* and & next to the type not name, e.g.,
    • Pointers void* p instead of void *p or void * p
    • References int& p instead of int &p or int & p
  • When using auto, to avoid confusion, write auto& v or auto* p to avoid ambiguity.

Usage of Brackets

class A {
};

struct B {
};

void doSomething() {
	if (condition) {
		doThis();
	} else {
		doThat();
	}
	while (condition()) {
		continue;
	}
	for (int i = 0; i < a; ++i) {
		break;
	}
}

Use nullptr

C++11 introduces nullptr which is a special value denoting a null pointer. This should be used instead of 0 or NULL to indicate a null pointer.

Indents

Use four spaces for one indent.

Comments

Comment blocks should use //. Using // makes it much easier to comment out a block of code while debugging.

// this function does something
int myFunc(){}

To comment out this function block during debugging we might do:

Never Use using namespace in a Header File

This causes the namespace you are using to be pulled into the namespace of all files that include the header file.

It pollutes the namespace and it may lead to name collisions in the future.

Writing using namespace in an implementation file is fine though.

Include Guards

Header files must contain a distinctly-named include guard to avoid problems with including the same header multiple times and to prevent conflicts with headers from other projects.

#ifndef MYPROJECT_MYCLASS_HPP
#define MYPROJECT_MYCLASS_HPP
namespace MyProject {
class MyClass {
};
}
#endif

Please do not use #pragma once instead of include guards. Our continuous integration workflow will transform #pragma once statements into include guards when merging pull requests.

Use "" for Including Local Files

As <> is reserved for system includes include local files with "".

// Bad Idea
// Requires extra -I directives to the compiler and goes against standards.

#include <string>
#include <includes/MyHeader.hpp>

// Worse Idea
// Requires potentially even more specific -I directives and
// makes code more difficult to package and distribute.
#include <string>

#include <MyHeader.hpp>

// Good Idea
// Requires no extra params and notifies the user that the file
// is a local file.

#include <string>

#include "MyHeader.hpp"

Initialize Member Variables With the Member Initializer List

For POD types, the performance of an initializer list is the same as manual initialization, but for other types there is a clear performance gain, see below.

// Bad Idea

class MyClass {

public:

MyClass(int t_value) {
	m_value = t_value;
}

private:

int m_value;

};

// Bad Idea
// This leads to an additional constructor call for m_myOtherClass before the assignment.

class MyClass {
	public:
	MyClass(MyOtherClass t_myOtherClass) {
		m_myOtherClass = t_myOtherClass;
	}
	private:
	MyOtherClass m_myOtherClass;
};

// Good Idea
// There is no performance gain here but the code is cleaner.

class MyClass{
	public:
	MyClass(int t_value): m_value(t_value){}
	private:
	int m_value;
};

// Good Idea
// The default constructor for m_myOtherClass is never called here, so
// there is a performance gain if MyOtherClass is not is_trivially_default_constructible.

class MyClass {
	public:
	MyClass(MyOtherClass t_myOtherClass) : m_myOtherClass(t_myOtherClass){}
	private:
	MyOtherClass m_myOtherClass;
};

In C++11 you can assign default values to each member (using = or using {}).

Assigning Default Values With =

// Good Idea

private:

int m_value = 0; // allowed

unsigned m_value_2 = -1; // narrowing from signed to unsigned allowed

This ensures that no constructor ever “forgets” to initialize a member object.

Assigning Default Values With Brace Initialization

Using brace initialization does not allow narrowing at compile-time.

// Good Idea

private:

int m_value{ 0 }; // allowed

unsigned m_value_2 { -1 }; // narrowing from signed to unsigned not allowed, leads to a compile time error

Prefer {} initialization over = unless you have a strong reason not to. Forgetting to initialize a member is a source of undefined behavior bugs which are often extremely hard to find. If the member variable is not expected to change after the initialization, then mark it const.

class MyClass
{
public:

MyClass(int t_value): m_value{t_value}
{}

private:

const int m_value{0};
};

Use the Correct Integer Type for Standard Library Features

The standard library generally uses std::size_t for anything related to size. The size of size_t is implementation dependent.

In general, using auto will avoid most of these issues, but not all.

Make sure you stick with the correct integer types and remain consistent with the C++ standard library. It might not warn on the platform you are currently using, but it probably will when you change platforms.

Note that you can cause integer underflow when performing some operations on unsigned values. For example:

std::vector<int> v1{2,3,4,5,6,7,8,9};

std::vector<int> v2{9,8,7,6,5,4,3,2,1};

const auto s1 = v1.size();

const auto s2 = v2.size();

const auto diff = s1 - s2; // diff underflows to a very large number

Never Put Code with Side Effects Inside an assert()

assert(registerSomeThing()); // make sure that registerSomeThing() returns true

The above code succeeds when making a debug build, but gets removed by the compiler when making a release build, giving you different behavior between debug and release builds.

This is because assert() is a macro which expands to nothing in release mode.

Don’t Be Afraid of Templates

They can help you stick to DRY principles. They should be preferred to macros, because macros do not honor namespaces and other important concepts.

Consider the Rule of Zero

The Rule of Zero states that you do not provide any of the functions that the compiler can provide (copy constructor, copy assignment operator, move constructor, move assignment operator, destructor) unless the class you are constructing does some novel form of ownership.

The goal is to let the compiler provide optimal versions automatically maintained when more member variables are added. Explicit examples of how to archive this are:

  • Use shared pointer instead of raw pointer for allocations
  • Expose factory methods for the construction of derived objects

Generated Methods

The generated methods that come from sources like GRPC, we start the function name with capital first letter