C++ Coding Standards
Preface
Rule of Thumb
- Readability first (your code should be your documentation most of the time)
- Crash/Assert early. Don't wait until the worst case happens to make the crash condition.
- Follow IDE's auto formatted style unless you have really good reasons not to do so. (Ctrl + K + D in VC++)
- Learn from existing code
References
This coding standards is inspired by these coding standards
I. Main Coding Standards
-
Use Pascal casing for class and structs
class PlayerManager; struct AnimationInfo; -
Use camel casing for local variable names and function parameters
void SomeMethod(const int someParameter); { int someNumber; int id; } -
Use verb-object pairs for method names
a. Use pascal casing for public methods
public: void DoSomething();b. Use camel casing for other methods
private: void doSomething(); -
However, if a method simply returns a boolean state, the verb part of the name should be prefixed Is, Can, Has or Should. If the function name becomes not natural by doing so, use the 3rd-person singular form of another verb.
public: bool IsAlive(const Person& person) const; bool HasChild(const Person& person) const; bool CanAccept(const Person& person) const; bool ShouldDelete(const Person& person) const; bool Exists(Person& person) const; -
Use ALL_CAPS_SEPARATED_BY_UNDERSCORE for constants and defines
constexpr int SOME_CONSTANT = 1; -
Use all lowercase letters for namespaces
namespace abc{}; -
prefix boolean variables with
bbool bFired; // for local and public member variable bool mbFired; // for private class member variable -
prefix interfaces with
Iclass ISomeInterface; -
prefix enums with
eenum class eDirection { North, South } -
prefix class member variables with
m.class Employee { protected: int mDepartmentID; private: int mAge; } -
Use ALL_CAPS_SEPARATED_BY_UNDERSCORE for
gotolabels.goto MY_LABEL; // .... MY_LABEL: std::cout << "Magic!" << std::endl; return 0; -
Methods with return values must have a name describing the value returned
uint32_t GetAge() const; -
Use descriptive variable names. e.g
indexoremployeeinstead ofioreunless it is a trivial index variable used for loops. -
Capitalize every characters in acronyms only if there is no extra word after them.
int OrderID; int HttpCode; -
Always use setter and getters for class member variables
Use:
class Employee { public: const string& GetName() const; void SetName(const string& name); private: string mName; }Instead of:
class Employee { public: string Name; } -
Use only public member variables for a struct. No functions are allowed. Use pascal casing for the members of a struct.
struct MeshData { int32_t VertexCount; } -
Use
#include<>for external header files. Use#include ""for in-house header files -
Put external header files first, followed by in-house header files in alphabetic order if possible.
#include <vector> #include <unordered_map> #include "AnimationInfo.h" -
Use
#pragma onceat the beginning of every header file -
Declare local variables as close as possible to the first line where it is being used.
-
Use precision specification for floating point values unless there is an explicit need for a
doublefloat f = 0.5f; -
Always have a
defaultcase for aswitchstatement.switch (number) { case 0: ... break; default: break; } -
Always add predefined FALLTHROUGH for switch case fall through unless there is no code in the
casestatement. This will be replaced by[[fallthrough]]attribute coming in for C++17 laterswitch (number) { case 0: DoSomething(); FALLTHROUGH case 1: DoFallthrough(); break; case 2: case 3: DoNotFallthrough(); break; default: break; } -
If default case must not happen in a
switchcase, always addAssert(false). In our assert implementation, this will add optimization hint for release build.switch (type) { case 1: ... break; default: Assert(false, "unknown type"); break; } -
Use
consts as much as possible even for local variable and function parameters. -
Any member functions that doesn't modify the object must be
constint GetAge() const; -
Do not return const value type.
Constreturn is only for reference and pointers -
Names of recursive functions end with
Recursive.void FibonacciRecursive(); -
Order of class variables and methods must be as follows:
a. list of friend classes b. public methods c. protected methods d. private methods e. protected variables f. private variables
-
Function overloading must be avoided in most cases
Use:
const Anim* GetAnimByIndex(const int index) const; const Anim* GetAnimByName(const char* name) const;Instead of:
const Anim* GetAnim(const int index) const; const Anim* GetAnim(const char* name) const; -
Overloading functions to add
constaccessible function is allowed.Anim* GetAnimByIndex(const int index); const Anim* GetAnimByIndex(const int index) const; -
Avoid use of
const_cast. Instead create a function that clearly returns an editable version of the object -
Each class must be in a separate source file unless it makes sense to group several smaller classes.
-
The filename must be the same as the name of the class including upper and lower cases.
class PlayerAnimation; PlayerAnimation.cpp PlayerAnimation.h -
When a class spans across multiple files, these files have a name that starts with the name of the class, followed by an underscore and the subsection name.
class RenderWorld; RenderWorld_load.cpp RenderWorld_demo.cpp RenderWorld_portals.cpp -
Platform specific class for reverse OOP pattern uses similar naming convention
class Renderer; Renderer.h // all renderer interfaces called by games Renderer.cpp // Renderer's Implementations which are // to all platforms Renderer_gl.h // RendererGL interfaces called by // Renderer Renderer_gl.cpp // RendererGL implementations -
Use our own version of Assert instead of standard c assert
-
Use assert for any assertion you have. Assert is not recoverable. This can be replaced by compiler optimization hint keyword __assume for the release build.
-
Any memory allocation must be done through our own New and Delete keyword.
-
Memory operations such as memset, memcpy and memmove also must be done through our own MemSet, MemCpy and MemMove.
-
Generally prefer reference(
&) over pointers unless you neednullptrfor any reason. (exceptions are mentioned right below) -
Use pointers for
outparameters. Also prefix the function parameters with out.function:
void GetScreenDimension(uint32_t* const outWidth, uint32_t* const outHeight) { }caller:
uint32_t width; uint32_t height; GetScreenDimension(&width, &height); -
The above out parameters must not be null. (Use assert, not if statement)
void GetScreenDimension(uint32_t* const outWidth, uint32_t* const outHeight) { Assert(outWidth); Assert(outHeight); } -
Use pointers if the parameter will be saved internally.
void AddMesh(Mesh* const mesh) { mMeshCollection.push_back(mesh); } -
Use pointers if the parameter should be generic
void*parametervoid Update(void* const something) { } -
The name of a bitflag enum must be suffixed by
Flagsenum class eVisibilityFlags { } -
Do not add size specifier for
enumunless you need that specific size (e.g, for serialization of data members)enum class eDirection : uint8_t { North, South } -
Prefer overloading over default parameters
-
When default parameters are used, restrict them to natural immutable constants such as
nullptr,falseor0. -
Prefer fixed-size containers whenever possible.
-
reserve()dynamic containers whenever possible -
Always put parentheses for defined numbers
#define NUM_CLASSES (1) -
Prefer constants over defines
-
Always use forward declaration if possible instead of using includes
-
All compiler warnings must be addressed.
-
Shadowed variables are not allowed.
class SomeClass { public: int32_t Count; public: void Func(const int32_t Count) { for (int32_t count = 0; count != 10; ++count) { // Use Count } } } -
Declare only one variable per line
BAD:
int counter, index;GOOD:
int counter; int index; -
Take advantage of NRVO, when you are returning a local object. This means you need to have only one return statement inside your function. This applies only when you return an object by value.
-
Do not use
constmember variables in astructorclasssimply to prevent modification after initialization. Same rule is applied to reference(&) member variables. -
When initializing member variables, use initializer list over assignments, by default.
-
«<__restrict keyword
II. Code Formatting
-
There must be a blank line between includes and body.
-
Use Visual Studio default for tabs. If you are not using Visual Studio, use real tabs that are equal to 4 spaces.
-
Always place an opening curly brace (
{) in a new line -
Add curly braces even if there's only one line in the scope
if (bSomething) { return; } -
Put pointer or reference sign right next to the type
int& number; int* number; -
When initializing member variables via initializer list, initialize one member variable per line and follow below format.
BAD:
MyClass::MyClass(const int var1, const int var2) :mVar1(var1), mVar2(var2), mVar3(0) {GOOD:
MyClass::MyClass(const int var1, const int var2) : mVar1(var1) , mVar2(var2) , mVar3(0) {
III. Modern Language Features
-
overrideandfinalkeywords are mandatory -
Use
enum classalwaysenum class eDirection { North, South } -
Use
static_assertoverAssert, if possible. -
Use
nullptroverNULL -
Use
unique_ptrwhen a object lifetime is solely handled inside a class. (i.e. new in constructor delete in destructor) -
Range-based
forare recommended where applicable -
Do not use
autounless it is for a iterator ornewkeyword is on the same line, showing which object is created clearly -
Do not manually perform return value optimization using
std::move. It breaks automatic NRVO optimization. -
Move constructor and move assignment operator are allowed.
-
Use
constexprinstead of const for simple constant variablesconstexpr int DEFAULT_BUFFER_SIZE = 65536Instead of
const int DEFAULT_BUFFER_SIZE = 65536 -
«<TBD: constexpr
-
«<TBD: Lambda
-
«<TBD: do not use shared_ptr
IV. Project Settings and Project Structure
-
Visual C++: Always use property sheets to change project settings
-
Do not disable compile warnings in project settings. Use #pragma in code instead.