Skip to content

Instantly share code, notes, and snippets.

@AbsintheScripting
Created September 6, 2023 13:04
Show Gist options
  • Save AbsintheScripting/4f2be73c91fc49fc6bc2cefbb2a52895 to your computer and use it in GitHub Desktop.
Save AbsintheScripting/4f2be73c91fc49fc6bc2cefbb2a52895 to your computer and use it in GitHub Desktop.
My C++ code style

Things to consider

  • 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

Includes

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>).

Filenames

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).

Declaration style

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.

Declaration order

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.

Naming Conventions

The following is only an overview, see the example for a better understanding.

Prefixes
  • 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
Special rules

There may be special rules in a project, but it must be stated here.

Naming Style
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

Example:

// 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment