- Scott Meyers - Effective Modern C++ (explains important C++11/14 features)
- C++ Core Guidelines (C++17/20, for naming and layout look at the examples below)
- Unreal Engine Coding Standard (ignore the UE specific stuff)
- Do not use the Google C++ Style Guide
Project's header files should be listed as descendants of the project's source directory or the current directory,
without use of UNIX directory aliases .
(the current directory) or ..
(the parent directory).
These headers are declared in quotation marks ("Your/Include.h"
).
Headers from outside this projects scope are declared in angle brackets (<Your/Include.h>
).
Files should be named after its class / struct (CClass.h
and CClass.cpp
for example).
If you have a collection of simple data-structs, you name it after the collection (like MyComponents.h
).
All declarations are in a .h
header file.
The implementation of methods and definition of members should be in the .cpp
source file.
If your header file has also implementations of methods / factories, use the .hpp
ending.
Group similar declarations together, placing public parts earlier.
A class definition should usually start with a public:
section, followed by protected:
and finally private:
.
Omit sections that would be empty.
Within each section, prefer grouping similar kinds of declarations together, and prefer the following order: types and type aliases (nested structs and classes, enum, aliases, ...), static constants, factorys, constructors and assignment operators, destructor, all other member and friend functions (also templated ones), data members.
The following is only an overview, see the example for a better understanding.
- typedefs and aliases (using) are prefixed by
T
- classes and structs are prefixed by
C
- except for interface and abstract classes, those are prefixed by
I
- enums and enum classes are prefixed by
E
- unions are prefixed by
U
- pointer and smart pointer variables and parameters are prefixed by
p
- boolean variables and parameters are prefixed by
b
There may be special rules in a project, but it must be stated here.
Entity | Style |
---|---|
Typedefs | TUpperCamelCase |
Classes and structs | CUpperCamelCase, IUpperCamelCase |
Class and struct methods | UpperCamelCase |
Class and struct fields | lowerCamelCase |
Parameters | all_lower, p_all_lower, b_all_lower |
Template Parameters | UpperCamelCase |
Enums | EUpperCamelCase |
Enum members | ALL_UPPER |
Unions | UUpperCamelCase |
Union members | lowerCamelCase |
Variables | lowerCamelCase, pLowerCamelCase, bLowerCamelCase |
Constants and Macros | ALL_UPPER |
Namespaces | UpperCamelCase |
Concepts | all_lower |
// Header File "CFoo.hpp"
#pragma once
#include <cstdint> // system and shared header in <>
#include <memory>
#include <type_traits>
#include <Lib/CFooBarBar.h> // group headers by directory
#include <Lib/CFooFooBar.h>
#include <Shared/CMath.h>
#include "CFooBar.h" // header inside the project directory in ""
#include "CMyBaseStruct.h"
// type constraints
// since concepts may act like a boolean function, we use the prefix is_ here
template <class T>
concept is_my_struct = requires { typename T::TMyStructType; };
template <class T>
concept is_from_my_base_struct = requires { typename T::TBaseType; } && requires(T obj)
{
std::is_base_of_v<CMyBaseStruct<typename T::TBaseType>, T>;
};
// but we can also use it as replacement for typename / class
// to constrain our type in the template declaration
// and to chain multiple concepts together
template <class T>
concept my_struct = is_my_struct<T> && is_from_my_base_struct<T>;
// use forward declarations whenever possible
class CBar;
/**
* Try to describe the class.
*/
class CFoo // classes start with capital C
{
// consider the following order
public:
// nested classes, structs, enums and unions first
enum class EFoo // enums start with capital E, use enum class.
{
FOO_FOO, // enum members are ALL_UPPER
BAR_BAR
};
union UFooBar // unions start with capital U
{
struct CVector // like classes structs begin with capital C
{
float x;
float y;
float z;
};
float v[3];
};
// types and type aliases second
using TFooInt = int32_t; // use using instead of typedef
// static constants
static constexpr auto FOO_CONSTANT = "bar"; // use constexpr, ALL_UPPER
// try to avoid static variables at all cost
// static functions
static void DestroyFoo(int32_t idx);
// factory
// use concepts directly in the template declaration if you can
template <my_struct T> // after template declaration -> new line
struct CMyStruct
{
typedef T TValueType;
CMyStruct() = delete;
// use explicit keyword for specialized constructors
explicit CMyStruct(TValueType my_member, int32_t some_more_member,
bool some_long_param_name); // indent correctly (mix of tabs and spaces)
// ^indentation is: tab tab (to reach the same indentation as "explicit CMyStruct")
// and the rest uses spaces until we reach the round bracket of the previous line
TValueType member;
int32_t someMoreMember;
};
// constructors
CFoo();
// operators
bool operator==(const CFoo& other_foo) const;
// destructor
~CFoo(); // whenever you use "new", you HAVE TO write also a delete!
// member and friend methods
[[nodiscard]] // after c++ attributes -> next line
int32_t GetBar(int32_t idx_param); // use lowercase with underscore for params
// functions should have a clear name what they are doing or returning
// in case of booleans prefer using "Is" and "Has"
template <typename T>
[[nodiscard]]
bool IsMember(T* p_member);
// public data member
// if this class is the owner of the object, we use smart-pointers
std::unique_ptr<CBar> pBar; // p prefix for (smart-)pointers
protected:
bool IsBar(CBar* p_bar);
bool bIsBar;
uint32_t numBars; // always use stdint types, don't use int, long ...
// const char* (c-style strings), a single char and bools are the only exception
private:
// data members
bool bIsMember; // b prefix for booleans
int32_t numberMember;
};
// factory implementation in the same header file.
template <typename T>
CFoo::CMyStruct<T>::CMyStruct(TValueType my_member, const int32_t some_more_member,
bool some_long_param_name) // indent correctly (mix of tabs and spaces)
// ^this identation is spaces only
: member(my_member),
someMoreMember(some_more_member)
// ^here the indentation is: tab space space
{
}
template <typename T>
bool CFoo::IsMember(T* p_member)
{
// use the if constexpr feature whenever possible
if constexpr (std::is_same<std::decay_t<T>, CFoo>())
return true;
return false;
}
// Source File "CFoo.cpp"
#include "CFoo.hpp"
#include "CBar.h" // don't forget to include your forward declared classes in the source file
// init all members here
CFoo::CFoo()
: pBar(new CBar()),
bIsBar(false),
numBars(0),
bIsMember(false),
numberMember(0)
{
}
bool CFoo::operator==(const CFoo& other_foo) const
{
// use next line for long boolean checks
return bIsMember == other_foo.bIsMember
&& numberMember == other_foo.numberMember
&& numBars == other_foo.numBars;
}
CFoo::~CFoo()
{
//delete pBar;
// UPDATE: raw pointers are non-owning
// so we use smart pointers and let them release the object
// see: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rr-ptr
}
// implement normal methods in the source file
int32_t CFoo::GetBar(int32_t idx_param)
{
return 0;
}