Coding Style Guidelines

Text Formatting

Tabulations

  • Use soft tabs with a width of 4 spaces.

  • Do not use hard tabs.

Do

void myFunction() {
    if (something) {
        std::cout << "Hello, world!" << std::endl;
    }
}

Don’t

// This indentation uses 2 spaces, which is incorrect
// Any other indentation width is also incorrect
void myFunction() {
  if (something) {
    std::cout << "Hello, world!" << std::endl;
  }
}

Language Features

Pointers and References

  • Use references over pointers where possible.

  • Use std::unique_ptr, std::shared_ptr and std::weak_ptr over raw pointers where possible.

  • Use reset(), or nullptr over NULL or 0

  • Avoid using new and delete

Do

std::unique_ptr<MyClass> myUniquePtr = std::make_unique<MyClass>();
std::shared_ptr<MyClass> mySharedPtr = std::make_shared<MyClass>();
std::weak_ptr<MyClass> myWeakPtr = mySharedPtr;

CStyleResource *cStyleResource = CStyleLibrary_CreateResource();

myUniquePtr.reset();
mySharedPtr.reset();

CStyleLibrary_DestroyResource(cStyleResource);
cStyleResource = nullptr;

Don’t

MyClass *myRawPtr = new MyClass();
MyClass *myRawPtr2 = NULL;
MyClass *myRawPtr3 = 0;

CStyleResource *cStyleResource = CStyleLibrary_CreateResource();

delete myRawPtr;

Reference to this

  • this can be used to refer to the current object.

  • You can omit this-> when accessing member variables.

Do

void MyClass::myFunction() {
    _myVariable = 42;
    this->_myVariable = 42;
}

Positioning of const, & and *

  • const on variables and member variables is always placed before the type.

  • & and * are always placed after the type, sticking to the name of variables and member variables.

  • & and * never stick to function names.

Do

int & myFunction(int &myVariable) {
    const int *myPointer = nullptr;
    int &myReference = myVariable;

    return myReference;
}

Don’t

int& myFunction(int& myVariable) {
    int* const myPointer = nullptr;
    int& myReference = myVariable;

    return myReference;
}

Virtual Methods and Functions

  • Use virtual to specify that a method can be overridden.

  • Use override to specify that you’ve overridden a virtual function.

  • Use final to specify a method cannot be overridden.

Do

class BaseClass {
public:
    virtual void myVirtualFunction() = 0;
    virtual void myOtherVirtualFunction() const;
};

class DerivedClass : public BaseClass {
public:
    void myVirtualFunction() override;
    void myOtherVirtualFunction() const override final;
};

Don’t

class BaseClass {
public:
    virtual void myVirtualFunction();
    virtual void myOtherVirtualFunction() const;
};

class DerivedClass : public BaseClass {
public:
    void myVirtualFunction();
    void myOtherVirtualFunction() const;
};

Preprocessor Directives

Include Guards

  • Use #pragma once for include guards.

  • Do not use #ifndef, #define, #endif for include guards.

Do

#pragma once

class TestClass {
public:
    // ...
};

Don’t

#ifndef TEST_CLASS_HPP
#define TEST_CLASS_HPP

class TestClass {
public:
    // ...
};

#endif

Include Directives

  • Include paths must be absolute to the project root.

  • Include directives should be grouped

  • Use quoted paths for internal includes, and angle brackets for external includes.

  • Use C++ to C header bindings where possible.

Do

// In src/engine or include/engine
#include "bboq/engine/Engine.hpp"

// Outside of src/engine or include/engine (e.g. in src/game/rtype)
#include <bboq/engine/Engine.hpp>

#include <SFML/Graphics.hpp>

#include <iostream>

#include <cstddef>
#include <cstdint>

Don’t

#include "Engine.hpp"

#include "SFML/Graphics.hpp"

#include "iostream"

#include "stddef.h"
#include "stdint.h"

Preprocessor Directives

  • Do not overuse preprocessor directives.

  • Use #if defined(...) over #ifdef and #ifndef.

  • Indentation should be consistent with the surrounding code.

Do

#if defined(BBOC_CLIENT) && defined(DEBUG)
    #if defined(SOMETHING_SOMETHING)
        std::cout << "This is another test" << std::endl;
    #endif

    std::cout << "Hello, world as a Debug binary!" << std::endl;
#endif

void testFunction() {
    #if defined(BBOC_SERVER)
        std::cout << "Goodbye, world!" << std::endl;
    #endif
}

Don’t

#ifdef BBOC_CLIENT
#ifdef DEBUG
std::cout << "Hello, world as a Debug binary!" << std::endl;
#endif
#endif

void testFunction() {
#ifdef BBOC_SERVER
        std::cout << "Goodbye, world!" << std::endl;
#endif
}

Namespaces

General Conventions

  • Use using namespace only in scopes where it is necessary, and never in header files.

  • Namespaces follow the PascalCase naming convention.

Engine

  • The engine (all files under /src/engine and /include/engine) uses the BBOC namespace.

Do

namespace BBOC {
    // ...
};

Don’t

namespace engine {
    // ...
};

// Don't do this in a header file
using namespace BBOC;

Games

  • Games use their own namespace.

Do

namespace RType {
    // ...
};

RType::...

Don’t

namespace game {
    // ...
};

namespace BBOC {
    // ...
};

Naming Conventions

General Naming

  • Use PascalCase for class, structure and type alias names.

Do

class MyClassName {
public:
    // ...
}

Don’t

class myClass_name {
public:
    // ...
}

Function Names

  • Use camelCase for function names.

Do

void myFunctionName() {
    // ...
}

Don’t

void MyFunctionName() {
    // ...
}

void another_function_name() {
    // ...
}

Variable Names

  • Use camelCase for variable names.

Do

bool thisIsAVariable;
int hello;
std::string myString;

Don’t

bool ThisIsAVariable;
int hello_world;
std::string My_String;

Constants

  • Use UPPER_CASE for constants.

  • Use constexpr where possible over const.

  • Never use #define statements for constants.

Do

class TestClass {
public:
    // `constexpr` can be used for primitive types, including those aliased with `using`
    static constexpr int MY_SUPERB_CONSTANT = 42;

    // Use `const` otherwise...
    static const MyObject MY_INCREDIBLE_OBJECT;
}

// And define your value in the associated .cpp file:
const TestClass::MyObject TestClass::MY_INCREDIBLE_OBJECT = MyObject();

Don’t

#define MY_SUPERB_CONSTANT 42

class TestClass {
public:
    static const int MY_SUPERB_CONSTANT = 42;

    static const MyObject myIncredibleObject;
}

Enumerations

  • Use UPPER_CASE for enumeration values.

  • Declare enumerations with enum class (C++ style) instead of enum (C style).

  • Do not use #define statements for enumerations or enumeration values.

Do

enum class MyEnum {
    FIRST_VALUE,
    SECOND_VALUE,
    THIRD_VALUE
};

enum class MyValueEnum : std::uint8_t {
    FIRST_VALUE = 0,
    SECOND_VALUE = 1,
    THIRD_VALUE = 2
};

Don’t

enum MyEnum {
    FIRST_VALUE,
    SECOND_VALUE,
    THIRD_VALUE
};

#define FIRST_VALUE 0
#define SECOND_VALUE 1
#define THIRD_VALUE 2

Classes and Structures

Member Variables

  • The indentation of public, protected and private sections must be the same as for the class definition.

  • Use private over protected whenever possible.

  • Member variables must be prefixed with _, regardless of their visibility.

  • Member variables should be declared at the bottom of the class definition.

Do

class TestClass {
public:
    void myFunction();

private:
    int _myTestVariable;
    bool _myOtherTestVariable;

    std::string _myPrivateVariable;
    std::vector<int> _myOtherPrivateVariable;
};

Don’t

class TestClass {
    protected: // unnecessary here
        int myTestVariable;
        bool myOtherTestVariable;

        std::string MyPrivate_Variable;
        std::vector<int> ___myOtherPrivateVariable;

    public:
        void myFunction();
};