272 lines
9.5 KiB
Markdown
272 lines
9.5 KiB
Markdown
# C++ Code Conventions
|
||
> Rev. 2, 15-09-2025, Wessel T <contact@wessel.gg>
|
||
|
||
> Proposals can be made in the form of a merge request to the `main` branch.
|
||
> Don't forget to update the [Changelog](#Changelog) when doing so.
|
||
> These proposals need to be reviewed and accepted by everyone.
|
||
|
||
A big number of rules can be enforced by using the [`.editorconfig`](./.editorconfig). It is
|
||
recommended to install the editorconfig plugin into your desired IDE to automate correction of
|
||
these formatting mistakes.
|
||
|
||
## C++ Core Guidelines
|
||
|
||
> The following rules have been adopted from the
|
||
> [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-default)
|
||
> Examples are given for every rule. Read them if you do not understand what a rule means.
|
||
|
||
* C: Classes and class hierarchies
|
||
* C.ctor: Constructors
|
||
* C.45: Don’t define a default constructor that only initializes data members;
|
||
use in-class member initializers instead.
|
||
* ES: Expressions and statements
|
||
* ES.dcl: Declarations
|
||
* ES.11: use auto to avoid redundant repetition of type names.
|
||
* ES.23: Prefer the {}-initializer syntax.
|
||
* ES.expr: Expressions
|
||
* ES.47: Use nullptr rather than 0 or NULL
|
||
* Enum: Enumerations
|
||
* Enum.3: Prefer class enums over “plain” enums.
|
||
* T: Templates and generic programming
|
||
* Template interfaces
|
||
* T.43: Prefer using over typedef for defining aliases
|
||
* R: Resource management
|
||
* R.smart: Smart pointers
|
||
* R.20: Use unique_ptr or shared_ptr to represent ownership.
|
||
* R.21: Prefer unique_ptr over shared_ptr unless you need to share ownership.
|
||
|
||
## Naming
|
||
|
||
* FileNames are written in PascalCase.
|
||
* namespace are one word lower case.
|
||
* Class/Struct names are PascalCase.
|
||
* Interfaces are named as `IClass`.
|
||
* Design patterns are written fully, e.g. `AbstractBehaviorFactory`.
|
||
* Function names are snake_case.
|
||
* Function arguments are snake_case.
|
||
* Variable names are snake_case.
|
||
* Constexpr variables are named snake_case.
|
||
* Macro's are named SCREAMING_SNAKE_CASE.
|
||
* Give a variable the shortest descriptive name, so do not shorten the name if
|
||
it makes the name ambiguous.
|
||
|
||
## Indentation and tabs
|
||
|
||
* Indentation is done with spaces, tabs are to be converted to 4 spaces (most IDEs
|
||
have a setting for this). So each level of indentation is 4 spaces.
|
||
* Ensure that commits do not add trailing whitespace (most IDEs have a setting for
|
||
trimming trailing whitespace).
|
||
* The maximum line length is 100 characters, but exceptions are allowed where longer
|
||
lines are more practical.
|
||
|
||
## Style choices
|
||
|
||
* Curly brackets start on the same line (`if (true) {`).
|
||
* Curly brackets may start on a new line if the statement is multiple lines long.
|
||
For example after a constructor initializer list.
|
||
* Lambda functions should have no spaces inbetween (`[](){}`).
|
||
|
||
## Files
|
||
|
||
* Put one class/struct in a file and name the file after that class/struct.
|
||
* Member structs may be in the same file as a class to avoid scattering of information
|
||
over many files.
|
||
* When the file contains something else choose a name that accurately represents
|
||
the contents.
|
||
* Use full names, so no abbreviations (e.g. `RouteFileGenerator` instead of `RouteFileGen`
|
||
or waypoint instead of wp)
|
||
* Avoid acronyms. Exceptions are made for company jargon, like `IMU` for `InertialMeasurementUnit`.
|
||
Do document the full name at the top of the file when using acronyms.
|
||
* Friend classes are not allowed.
|
||
* File include paths may be global `include/ros/File.hpp` or relative `File.hpp`.
|
||
Relative include paths must be the same directory or a child, so there can not
|
||
be any `../` in include paths.
|
||
|
||
For C++ file types we use the following conventions:
|
||
|
||
* C++ header (\*.hpp)
|
||
* C++ source (\*.cpp)
|
||
|
||
Using C libraries is allowed, but any includes must be in an `extern c` block.
|
||
|
||
### ROS2 specific naming conventions
|
||
|
||
> **NOTE** Year is always in YYYY format (2025)
|
||
|
||
* Workspace names are snake_case, named as `<groupname>_<year>_ws`.
|
||
* Package names are snake_case, named as `<groupname>_<year>_<functional_name>_pkg`.
|
||
This name has to be approved by the project manager.
|
||
* Interface package names are snake_case, named as either `<groupname>_<year>_machine_interfaces`
|
||
or `<groupname>_<year>_assessment_interfaces`
|
||
|
||
## Comments
|
||
|
||
* Comments are lines starting with `//`.
|
||
* Header files have a short comment at the top that explains the purpose of the
|
||
file's contents.
|
||
* Functions can get a comment for extra explanation if necessary.
|
||
* Avoid adding comments for variables or function parameters that do not add any
|
||
value. (e.g. @ returns bool value <// returns value)
|
||
* If code is commented out, remove it.
|
||
|
||
### Namespaces
|
||
|
||
* Code in a namespace is not indented.
|
||
* Avoid the `using namespace` instead write out the name of what you need. (e.g.
|
||
write `std::cout<<`, instead of `using std; .... cout<<`)
|
||
* `using namespace` is only allowed in small scope (short function, loop). Never
|
||
expose them in the header file.
|
||
* `using namespace` is also allowed in test files as they are not included anywhere
|
||
else.
|
||
|
||
## Classes
|
||
|
||
### Access modifiers (public/private/protected)
|
||
|
||
* Access-modifiers have no indentation (level 0), but everything inside them has
|
||
level 1 indentation. So member functions/variables have level 1 indentation.
|
||
* All members in a class have to be ordered by their access-modifier, so in the
|
||
following order:
|
||
* public
|
||
* protected
|
||
* private
|
||
* In a struct generally everything is public, so no access modifiers are needed.
|
||
* Avoid using public member variables in classes, as it breaks encapsulation.
|
||
|
||
## Functions
|
||
|
||
* Getters and setters do not get the `get_ / set_` prefix.
|
||
* FUnctions that invoke asynchronous operation without blocking must get the `_async` suffix.
|
||
* Declare functions as `const` where possible.
|
||
* Declare functions with `override` when overriding a base class function.
|
||
* Functions declared as pure virtual must not have an implementation.
|
||
* Pass in function arguments as const ref to avoid copying or changing variables
|
||
when not necessary. Simple types, such as int, float etc.. can be passed in
|
||
regularly as they are easily copied.
|
||
* Avoid side effects in a function (i.e. a function does something extra that is
|
||
not clear from the name), like a getter also incrementing a counter. Separate this
|
||
logic out into a different function.
|
||
* The body of an if-statement must be surrounded by braces `{}`.
|
||
* Functions private to a .cpp file must be marked `static`.
|
||
|
||
### Function complexity
|
||
|
||
Limit the complexity of the functions you write by cutting them up into multiple
|
||
functions or creating classes to handle some of the work. In general: a function
|
||
should do only one thing.
|
||
|
||
* Command/query separation: a function should be either be a command (i.e. a
|
||
function that does something like add_numbers, save_file) or a query (i.e. a
|
||
function that retrieves information, like get_state).
|
||
* Maximum function length is 60 lines.
|
||
* The maximum number of function arguments is 4.
|
||
* The maximum cyclomatic complexity of a function is 20. (i.e. the amount of
|
||
paths your function can take. So one if statement results in 2 paths, and 2
|
||
nested if statements are 4)
|
||
* Avoid passing in boolean arguments as flag arguments (i.e. the boolean
|
||
determines which path the function takes). If possible split it up into two
|
||
functions instead.
|
||
* Limit the stacking of conditionals (e.g.
|
||
`if (is_charging && (!is_free || moving))`). Instead wrap parts in a variable
|
||
or simplify the if statement by removing the second part and wrapping that and
|
||
the underlying code in a function.
|
||
* Any relevant calculation should output a log message of severity `DEBUG`.
|
||
|
||
## Variables
|
||
|
||
* private and protected member variables in a class have a suffix `_` (e.g. bool turned_on_)
|
||
* public member variables are named without suffix.
|
||
* Initialize member variables in: (sorted by preference)
|
||
* in-class initializer
|
||
* constructor initializer list
|
||
* constructor body
|
||
* Wrap the following in a variable to increase clarity:
|
||
1. magic numbers
|
||
2. magic strings
|
||
3. ternary statements (e.g. int module = is_connected ? 5 : 4)
|
||
4. boundary condition in loops (e.g. uint loop_end = vector.size() - 1)
|
||
* Prefer constexpr variables over macros.
|
||
* Avoid double pointers.
|
||
|
||
## Dates
|
||
|
||
Dates should always be written in `DD[-/]MM[-/]YYYY HH[:]mm[:]ss` or epoch format.
|
||
|
||
## Example file
|
||
|
||
The code below serves as a visual example of the style guidelines, e.g. where do
|
||
the brackets go, when is an indentation used, where do we add an whitespace.
|
||
This example is included, because rules about code style are often easier to see
|
||
than to read.
|
||
|
||
```c++
|
||
// Header file commentary
|
||
#pragma once
|
||
|
||
namespace foo::bar {
|
||
|
||
// When forward declaring a class you can choose to use indenting.
|
||
namespace robot {
|
||
class SimpleIdea;
|
||
}
|
||
|
||
// Use a comment only if it adds extra information
|
||
class ComplexIdea {
|
||
public:
|
||
|
||
ComplexIdea();
|
||
|
||
void some_function(int lower_limit);
|
||
|
||
private:
|
||
int the_answer_;
|
||
};
|
||
|
||
} // namespace foo::bar
|
||
|
||
#endif
|
||
```
|
||
|
||
```c++
|
||
namespace foo {
|
||
namespace bar {
|
||
|
||
extern "C" {
|
||
#include <c-library.h>
|
||
}
|
||
|
||
#include <cpp-library>
|
||
|
||
#include "custom-class.hpp"
|
||
|
||
ComplexIdea::ComplexIdea() : the_answer_(42) {
|
||
} // Headings should be surrounded by blank lines
|
||
|
||
void ComplexIdea::some_function(int lower_limit) {
|
||
|
||
int minimum_value = 1337;
|
||
if (lower_limit < minimum_value) {
|
||
return;
|
||
}
|
||
|
||
int lower_limit_squared = lower_limit * lower_limit;
|
||
|
||
// Note the indenting, the location of the { and the space before the {.
|
||
if (the_answer_ > lower_limit) {
|
||
the_answer_ = lower_limit;
|
||
} else if (the_answer_ < squared) {
|
||
the_answer_ = lower_limit * 0.5;
|
||
}
|
||
}
|
||
|
||
} // namespace bar
|
||
} // namespace foo
|
||
```
|
||
|
||
## Changelog
|
||
|
||
[15-09-2025] Wessel T: Add ROS2 naming standard
|
||
|
||
[12-09-2025] Wessel T: Create initial document
|
||
|