feat(cpp-convention): Add initial version
This commit is contained in:
254
CPP_CODE_CONVENTION.md
Normal file
254
CPP_CODE_CONVENTION.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# C++ Code Conventions
|
||||
> Rev. 1, 12-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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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 {
|
||||
namespace 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 bar
|
||||
} // namespace foo
|
||||
|
||||
#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
|
||||
|
||||
[12-09-2025] Wessel T: Create initial document
|
||||
Reference in New Issue
Block a user