General Guidelines
On this page
- Common C++ Naming Conventions
- Position of Operators
- Usage of Brackets
- Use
nullptr
- Indents
- Comments
- Never Use
using namespace
in a Header File - Include Guards
- Use
""
for Including Local Files - Initialize Member Variables With the Member Initializer List
- Use the Correct Integer Type for Standard Library Features
- Never Put Code with Side Effects Inside an assert()
- Don’t Be Afraid of Templates
- Consider the Rule of Zero
- Generated Methods
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 ofvoid *p
orvoid * p
- References
int& p
instead ofint &p
orint & p
- Pointers
- When using
auto
, to avoid confusion, writeauto& v
orauto* 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