standards_web_logo

Coding Standards

v1.1.1

Table of contents

Overview

This is ConstantRobotics’ Coding Standards document. This guide outlines essential principles for collaboration, code maintenance, libraries usage, and readability. By following these guidelines, we aim to maintain efficiency, and code quality. The repository also provides templates for libraries and applications with documentation examples: the folder templates contains the library template TemplateLibrary and the application template TemplateApplication. The repository does not contain sub-repositories: the ConfigReader library, which the templates depend on, must be placed in the same folder as the CodingStandards repository (to build both templates together) or in the same folder as a template which is copied to its own repository (see the README.md file of the template). In the RapidPixel SDK the CodingStandards folder is documentation only: the SDK does not build the templates, and the ConfigReader library is in the folder modules/Service/ConfigReader, not next to CodingStandards. To build a template, copy it out of the SDK and place a copy of the ConfigReader folder next to it.

Versions

Table 1 - Document versions.

Version Release date What’s new
1.0.0 04.01.2024 First version of standards, which includes:
- Style guide.
- Memory management guide.
- Templates for library and application with documentation guide.
- List of resources.
1.0.1 17.05.2024 - Documentation and structure updated.
1.1.0 06.08.2024 - Library template updated.
1.1.1 23.10.2026 - Templates do not contain sub-repositories: the library they depend on is placed in the same folder.
- Library template: setting of parameters, serialization and thread safety fixed, tests extended, warning and hardening flags added to the CMake files.
- Application template: parameters from the file are validated, description fixed.
- Table of contents, examples and typos fixed.
- All code is placed in the namespace rapidpixel: rules of namespaces and templates updated.

Coding Guide

The main point of coding guide is to provide a set of main rules that will be fundamental in our repositories structure. Our code should be optimized for the reader, not for the writer. The most important is efficiency, but to build up expandable and easy to maintain coding projects we have to follow basic standards.

General rules

  1. Follow the ‘Boy Scout Rule’. Always leave the code cleaner than you found it.
  2. Everything you write has to be compatible with C++ 17. Any departure from this principle has to be clearly mentioned in the documentation of the repository.
  3. Always include “README.md” file to the repository.
  4. If there is a chance that your class will be used as a base class, always keep destructor virtual.
  5. No destructor is always better when it’s the correct thing to do. If you don’t need destructor, don’t define the one. This rule is known as Rule of zero.
  6. When you define a destructor in a class, it’s important to also explicitly define or delete all constructors and assignment operators. This ensures proper management of resources, as the compiler’s implicit versions may not handle resource management correctly. This rule is known as Rule of three (until C++11) or Rule of five (after C++11).

Style

Code is read far more often than it is written, so a uniform style matters more than any single rule: when every file of a repository looks the same, a reader recognizes its structure at once. Prefer the conventions that most C++ developers already know. Long identifiers cost nothing in C++, so pick names that tell what a thing is or does and don’t shorten them.

Naming

  1. Use PascalCase for naming: a class, an enum class or a struct, e.g. - TemplateLibrary.

  2. Use camelCase for naming: a local variable, function or a method, e.g. - setParam().

  3. Name a constant in capital letters separated by underscores, e.g. - MAX_CONSTANT_VALUE.

  4. Distinguish private class field with a ‘m_’ prefix, e.g. - m_frameWidth (m_ stands for “member”).

  5. Distinguish global variables with a ‘g_’ prefix, e.g. - g_frameWidth (g_ stands for “global”).

  6. No name begins with an underscore ‘_’ (such names are reserved for the compiler and the standard library).

Composition

  1. In comment blocks use //, not /* */. /* */ is reserved for documentation in header file only.

  2. Strive to strike a balance in commenting your code - while it is crucial to provide sufficient comments for clarity and understanding, aim to keep them concise and focused, avoiding unnecessary verbosity to maintain code readability and promote efficient collaboration:

    // Good comment:
    // This equation comes from mass-energy equivalence E=mc^2.
    auto energy = mass * speedOfLight * speedOfLight;
    
    // Bad comment (code explains itself, comment is redundant):
    // Check if energy was calculated.
    if (energy.isCalculated())
    {
        use(energy);
    }
    
  3. Every block gets curly brackets {}, also a block of one statement. A statement added later to a block without brackets silently ends up outside of it:

    // Wrong: the indentation suggests that both lines belong to the loop,
    // but only the first one does.
    for (int index = 0; index < count; ++index)
        sum += values[index];
        ++processed;
    
    // Right: the brackets show where the loop ends.
    for (int index = 0; index < count; ++index)
    {
        sum += values[index];
    }
    ++processed;
    
  4. Take whole line for a single curly bracket (just to keep the same visual style everywhere).

    // Like this:
    if (frameCount == 0)
    {
        resetStatistics();
    }
    // Not like this:
    if (frameCount == 0) {
        resetStatistics();
    }
    
  5. Lines must stay short enough to read without scrolling. A line of a header file has at most 80 characters.
  6. Do not add namespaces for libraries: the code of all libraries is placed in the one namespace rapidpixel (see Good practices). A nested namespace is used only for internal code. Its name should be descriptive and short and contain the name of the library, for example rapidpixel::mserver_impl. Before adding a new nested namespace check if it doesn’t exist in different form.
  7. Keep 3 blank lines between each method or function body.

Documentation

  1. Each class, method, field or function has to have descriptive documentation written as a comment in the header file.

  2. Use /* */ only for documentation:

    /**
     * @brief Set the value for a specific template parameter.
     * @param id The identifier of the template parameter.
     * @param value The value to set for the parameter.
     * @return TRUE if the parameter was successfully set, FALSE otherwise.
     */
    bool setParam(TemplateParam id, float value);
    
  3. Keep the documentation up-to-date when making changes to the code.

Good practices

  1. A pointer that points to nothing is written as nullptr, not as NULL or 0.

  2. A header file never contains using namespace: the directive would take effect in every file that includes the header. Avoid it in source files too, because names of different namespaces can clash. Why is using namespace is considered a bad practice.

  3. Apply include guards to avoid including the same file multiple times. Use #pragma once.

  4. Always declare new classes, enums, functions and constants in the namespace rapidpixel. All libraries of the RapidPixel SDK use this one namespace, so the names must be unique within the SDK: start the names with the name of the library or of its interface, for example TemplateLibraryParam.

    #pragma once
    
    namespace rapidpixel
    {
    class TempClass
    {
    public:
        TempClass();
    };
    }
    
  5. Always initialize variables. Initialize variables with curly brackets to avoid narrowing chosen value:

    unsigned value = -1; // Compiles: -1 silently turns into the largest unsigned value.
    unsigned valueB{-1}; // Compile time error: braces reject the lossy conversion.
    
  6. When building a class with fields, always initialize member variables in header file, not in the constructor.

  7. Convert types with the named casts static_cast<>, dynamic_cast<>, const_cast<> and reinterpret_cast<>, never with the C notation (type)value. Each named cast performs only one kind of conversion, so the compiler refuses a conversion that was not meant, and every cast is easy to spot and to search for. Further reading.

  8. Avoid using macros for constant values. Use constexpr:

    not #define FRAME_RATE 30.0,

    but constexpr double FRAME_RATE{30.0};.

  9. Use scoped enums (enum class). They prevent implicit conversions to other types, reducing bugs, and avoid name conflicts by scoping enumerators within the enum.

    enum class Color
    {
        RED,
        GREEN,
        BLUE
    };
    
  10. Use override keyword for all polymorphic member functions, this way compiler will report an error if you made mistake while overriding a method.

    class Base
    {
    public:
        virtual ~Base() = default;
        virtual void foo() = 0;
    
    protected:
        Base() = default;
        Base(const Base& src) = default;
        Base(Base&& src) = default;
        Base& operator= (const Base& src) = default;
        Base& operator= (Base&& src) = default;
    };
    
    class Derived : public Base
    {
    public:
        void foo() override;
    };
    
  11. Try to avoid using global state. If you can’t avoid using them at least try to make them internally linked by using unnamed namespaces.

Memory Management

  1. Let objects own the memory: std::unique_ptr, std::shared_ptr and the standard containers free it by themselves, also when an exception is thrown. Hand-written new / delete (or malloc / free) is the usual cause of leaks and double frees; where a raw pointer can’t be avoided, check every path that allocates and releases the memory.

  2. Mark everything that is not supposed to change as const: the reader sees at once what may be modified, and the compiler reports an accidental change.

  3. A method that only lets the caller look at a large member can return it as const & and save a copy. Return by value when the caller keeps the result anyway or when the object can change in another thread: the copy is needed then and protects the caller.

  4. Never return nullptr from a function. When a function returns pointer and some condition is not satisfied, just return pointer to the empty value.

  5. Shared variables. Use std::atomic for basic types. Use std::mutex with std::lock_guard for operating with shared variables, in cases like parameters classes. The lock is released automatically, also if an exception is thrown:

    std::atomic<bool> templateVariable{false};
    templateVariable.store(true);
    
    TemplateParams templateParams{};
    std::mutex templateParamsMutex;
    
    {
        const std::lock_guard<std::mutex> lock(templateParamsMutex);
        templateParams.firstParam = true;
    }
    
  6. If semantically correct prefer ++i over i++. For iterators and other classes the prefix form can be faster: the postfix form has to keep the old value in a temporary object. For built-in types both forms are equally fast.

Code Management

Commits

  1. Commit as often as possible. Don’t wait until you finish the task with committing. Wrong combination of Ctrl+Z and Ctrl+Y may cost you losing a lot of code while prototyping. Frequent commits allow you to make smaller, focused changes to your codebase. Each commit represents a single logical unit of work or a specific feature/fix. This granularity makes it easier to understand the history of changes, identify the cause of issues, and review or revert changes if needed.

  2. Write your commit messages in the imperative mood, as it follows same style as commits generated by git commands.

    # Bad practice. Using dots at the end is also a bad idea.
    git commit -m "Version number in documentation updated."
    # Good practice.
    git commit -m "Update version number in documentation"
    
  3. Make your commit message descriptive, but not longer than 80 chars.

    # Bad practice.
    git commit -m "Fix bug"
    # Good practice.
    git commit -m "Fix memory leak after copying frame bug"
    

Branches

  1. A new Branch should be created each time you want to fix something or add a new feature to the master Branch.
  2. Connect Branch with Task from Projects board on GitHub. Create name related to Task name.
  3. A Branch should be closed automatically by Pull-request after merge.

Pull-requests

  1. Pull-request should be named the same as a branch.
  2. Pull-request can be closed only after approval from all reviewers.
  1. Visual Studio Code - simple and powerful IDE.
  2. Visual Studio for Windows - the most complex C++ IDE.
  3. Qt Creator - Qt IDE with Qt GUI tools.
  4. Sourcetree - very convenient desktop application for git control.

Resources

C++ best practices:

  1. https://github.com/cpp-best-practices/cppbestpractices
  2. https://google.github.io/styleguide/cppguide.html
  3. https://isocpp.org/wiki/faq/coding-standards
  4. Clean C++ - Stephan Roth, 2017

Clean Code tips:

  1. https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29

General standards:

  1. https://www.devbridge.com/articles/coding-best-practices/
  2. https://medium.com/agile-adapt/composing-meaningful-tasks-c1ca51064c1a

Table of contents