Coding conventions for Infinite projects written in C++
This document presents C++ code conventions we use at Infinite. This can help contributors understand our principles to read and write better code.
The goal of this repository is to keep everything simple and understandable.
Keep the code as simple as possible and understandable. Have consistency by using the same coding style and the same semantic for all the codebase. Use modern C++
In short, we name all variables, functions, methods and members in snake_case for better visibility and to follow the standard library of C++. Types, class enums, and struct names must be in PascalCase, members must end with _ to indicate that it is a member of a class.
Regarding brace placement, we prefer the K&R style.
A readable function should be fewer than 50 lines, and a well-structured file fewer than 600 lines.
Use the most modern C++ standard possible, within the constraints of the project.
Avoid manual memory management, prefer RAII and value semantics
Name your functions and variables with consistency and have logical naming.
Consistency is key, whatever casing convention a project uses, respect it and don't try to code in a different semantic.
Data structure and data organisation is one of the most important things. Use structs as function parameters instead of using 6+ parameters for example.
Do not comment everything, because good code is understandable by a beginner. But it is important to add a brief description at the beginning of files, definitions, groups of declarations and even on certain complex parts of the code.
Simple code that a beginner can understand is the goal.
Here are the general rules to make the code safe, readable, and maintainable:
- A good function length is under 50 lines
- A good file length is under 600 lines
- One function does one thing
- One file contains functions and things belonging to the same feature group
- Avoid manual memory management, prefer RAII and value semantics
- Use modern C++, avoid old C++ or C-style code in a C++ context
- Whatever naming convention we choose, use the same convention for the entire codebase
This document was written around the C++20 standard, targeting modern applications and programs.
| File extension | For what |
|---|---|
.cpp |
for C++ sources |
.hpp |
for C++ headers (never use .h for C++ files) |
.inl |
for C++ inline features |
.c |
for C sources |
.h |
for C headers |
We like using snake_case for files too. It is understandable, and we can divide a single header declaration into different source definitions.
content_browser.hpp
content_browser.cpp
content_browser_navigation.cpp
content_browser_rendering.cpp
We use two different project structure styles:
mainfolder style, containing all header and source files organized by functionality. example :
/main
/main/engine
/main/engine/renderer.hpp
/main/engine/renderer.cpp
/main/editor/viewport.hpp
/main/editor/viewport.cpp
/main/physics
/main/physics/velocity.hpp
/main/physics/velocity.cpp
srcandincludefolders style:
/include
/include/engine
/include/engine/renderer.hpp
/include/editor/viewport.hpp
/include/physics
/include/physics/velocity.hpp
/src
/src/engine
/src/engine/renderer.cpp
/src/editor/viewport.cpp
/src/physics
/src/physics/velocity.cpp
Regarding brace placement, we prefer the K&R style.
int foo() {
return 42;
}if() {
// code here
} else {
// code here
}We use brackets even if we have only one call in the condition
// bad
if(user.points() > POINTS_TO_WIN)
user.trigger_winner_menu();
// better
if(user.points() > POINTS_TO_WIN){
user.trigger_winner_menu();
} We prefer using snake_case for everything, it is easier to read. It is also the choice of the C++ standard library, and it is close to the STL. (Yes, it is a little bit more difficult with the typing of _, but no casing is perfect)
PascalCase and camelCase are acceptable, but consistency within the codebase is what matters. Do not use multiple casing styles in the same codebase.
int vehicleSpeedInMph; // Not easily readable
int VehicleSpeedInMph; // Not easily readable
int mVehicleSpeedInMph; // Even worse
int vehicle_speed_in_mph; // Better// Not easily readable
void SendMessageToServer(const std::string& clientMessage) {
auto& server = GetServer();
server->Send(clientMessage);
mLatestMessage = clientMessage;
}
// Better
void send_message_to_server(const std::string& client_message) {
auto& server = get_server();
server->send(client_message);
latest_message_ = client_message;
}Correctly naming your functions, variables and structures is one of the keys to readability. We need to know what a method or a function does only by reading its name, and we need to know what a variable or member is only by reading its name.
// Bad
void draw(...) // Draw what?
void send(...) // Send what to who?
void initialize(...) // Initialize what?
// Better
void draw_circle(...)
void send_to_server(...)
void initialize_interface(...)// Bad
int n; // What is this integer, what is it doing?
std::string name; // Whose name?
float radius_; // Radius of what?
// Better
int number_of_users;
std::string user_name;
float rectangle_radius_;But sometimes, simplicity is acceptable if it is comprehensible.
int i; // loop index
int x, y; // 2D coordinates
float t; // normalized time [0, 1]Variables must be named in snake_case and their name must clearly describe what they represent.
int number; // Simple variable
int another_number; // Variable with multiple words
constexpr int MAX_SPEED = 42; // Constant number
static const int NUMBER = 42; // Static constant numberFor all function types, we prefer snake_case to guarantee visibility and a clear distinction.
void some_function();
static void some_static_function();Class names must be in PascalCase. Methods and members follow the same naming rules as functions and variables.
class Car {
public:
void drive();
void trigger_alarm();
void set_color(const std::string& hex_color);
private:
int mph_speed_;
};Class members follow the same naming rules as variables, but must end with _ to distinguish them from local variables.
int number_; // Member of a class
std::string name_; // Member of a classFor all method types, we prefer snake_case to guarantee visibility and a clear distinction.
void some_method();
void another_method();
int another_method_with_int_return();Good use of structs is a great advantage for code coherence and consistency. Structs help organize data and avoid code duplication.
For example, for functions or methods that take many parameters, use structures to aggregate those values instead of having 6+ parameters.
// bad declaration
void draw_horizontal_item_card(
const std::string &name,
const std::string &path,
const std::string &description,
const std::string &size,
const std::string &logo,
const std::shared_ptr<ItemIdentifierInterface> &item_ident);
// bad use (magic parameters)
draw_horizontal_item_card(
filename_string,
path,
item_entry.first->description,
folder_size_string,
item_entry.first->logo_path,
item_entry.first,
);This is better, more readable and understandable:
// This struct is intended for call-site use only and must not outlive its parameters.
struct ItemCardContent {
const std::string &name;
const std::string &path;
const std::string &description;
const std::string &size;
const std::string &logo;
const std::shared_ptr<ItemIdentifierInterface> &item_ident;
};
// better declaration
void draw_horizontal_item_card(const ItemCardContent &content);
// better use
draw_horizontal_item_card({
.name = filename_string,
.path = path,
.description = item_entry.first->description,
.size = folder_size_string,
.logo = item_entry.first->logo_path,
.item_ident = item_entry.first,
});Structs help keep the code organized and limit code duplication.
Avoid using the using keyword. Prefer full namespaces to avoid confusion.
Use namespaces to separate the main APIs and contexts of your project. It makes it easy to distinguish between groups of features.
project::scripting::... // We know that this is scripting
project::rendering::... // We know that this is renderingUse scope brackets {} to isolate redundant code blocks that share similar variable names.
// Save button
{
const std::string label = "Save";
if(ui::button(label)) {
// code
}
}
// Refresh button
{
const std::string label = "Refresh";
if(ui::button(label)) {
// code
}
}Good code should be self-documented. Comments are important to help understanding, but good code does not need too many comments. Good code is simple, and simple code is comprehensible by itself.
// Do we really need a comment here? No.
if(vehicle_speed_in_mph > 100) {
catch_plate_with_photo();
}Each function, class, struct or enum should have a brief description at the top.
//
// print_hello_to_user
// Prints hello to the user name and returns a number.
//
int print_hello_to_user(const std::string& username) {
std::cout << "Hello, " << username << std::endl;
return 42;
}Each file should have a brief description at the top.
//
// main.cpp
// This file contains the main functions to print hello to a user name.
//
// Copyright (c) 2022 Owner
//
// This work is licensed under the terms of the LICENCE license.
// For a copy, see <https://opensource.org/licenses/LICENCE>.
//Do not use the Tab key. Because every editor handles it differently, prefer 2 or 4 spaces. You can use clang-format to do that automatically.
Good memory management will guarantee memory safety.
- Do not use C style memory management in C++
- Have the same memory management method for an entire context
- Prefer smart pointers (
std::unique_ptr,std::shared_ptr) over raw pointers - Avoid manual
newanddelete
- Use modern C++ features like mutex, semaphores or atomic members and variables to secure access and avoid race conditions
- Always use
constif possible (for a member, a variable, an object or whatever) - Prefer references over copies to avoid unnecessary object duplication
- Use
autofor complex types when it does not create confusion
Data structure and data organisation is one of the most important things for code safety and quality.
- For a group of variables or members, prefer using structures
C++ (in our choice C++20, and even more with C++23) offers a lot of modern features to create powerful and safe code.
- Use modern loops
// bad
for(int i = 0; i < words.size(); i++) {
std::string word = words[i];
// code here
}
// better
for(auto& word : words) {
// code here
}- Prefer structured bindings for pairs and tuples
// bad
auto pair = get_user();
std::string name = pair.first;
int age = pair.second;
// better
auto& [name, age] = get_user();- Use
std::optionalinstead of returning null or error codes
// bad
User* find_user(int id); // nullptr if not found
// better
std::optional<User> find_user(int id);- Prefer
enum classover rawenum
// bad
enum Direction { Left, Right, Up, Down };
// better
enum class Direction { Left, Right, Up, Down };- Use modern C++ standard features like
std::move,std::find,std::functionand more.
This publication may change to add new things, or correct eventual errors and imprecisions. We will keep this document clear and concise, without overloading it with unnecessary information.