Files
ros2-conventions/CPP_CODE_CONVENTION.md

272 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: Dont 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