Home / C++ / Code Organization
C++ Code Organization
Base rules for all code organization:
- Files, classes, etc. should be organized with the reader in mind rather than the writer.
- Access should be restricted on a minimal API and expose as little of the implementation as possible.
File Organization
- Prefer creating new files for each type over bundling multiple types together.
- Prefer to create header files over delcaring types in cpp files. Espcially if the declaration is longer than 5-10 lines
- One exception: Utility types used for tests (e.g. mocks) may be created at the beginning of the test file.
- Use public/private folders for plugin source code to separate public header files from private source files
- Consider grouping POD type declarations and interface declarations in separate folders
- Public and private folders should have mirrored subfolders and filenames wherever applicable so related header and source files can be found at matching relative paths, e.g.
- Header:
Public/Components/InventoryComponent.h
Source: Private/Components/InventoryComponent.cpp
- Header:
MyGameModule/Traffic/VehicleMovement.h
Source: MyGameModule/Traffic/VehicleMovement.cpp
- Use similar names as engine source code for folders if you write code that could also be part of the engine on project/plugin level, e.g.
- Type traits -> Traits/
- Containers -> Containers/
- Math types and utilities -> Math/
- All declarations belong in header files
- If a type is restricted to internal/private, use a header file in the private folder instead of declaring it directly in the source file
- Exception #1: Macro based declarations of utility types like log categories, stat groups, etc
- Exception #2: Super short utility functions (< 15 lines of code)
- Header files follow this structure:
- Copyright
#pragma once
- Includes
- engine includes
- custom code includes
- generated header file
- Forward declarations
- One or more “main type” declarations, each consisting of
- Macro based utility type declarations required for main type (log categories, delegate types, etc)
- Main type (e.g. a uclass, uenum, etc)
- Dependent type overloads (e.g. LexToString overlaod, type traits)
Source File Organization
Source files follow this structure:
- Includes
- associated header file
- other headers sorted alphabetically
- Type declarations (limited as described in Header File Organization)
- External variable/member definitions
- Function definitions in the same order as the declarations in the header file
Class Member Organization
Use the following sorting of members:
- Access Level
- Sort declarations by access level: public, protected, private
- One big continuous block per access specifier
- Prefer private over protected and protected over public
- Expanding access later on is a lot easier than restricting access
- Member Type
- Constructors
- Static member functions providing access to the class itself
- Nested types
- Member Fields
- uproperties
- non-uproperties
- Member Functions
- All overrides grouped by parent type like this:
// - ParentTypeName
virtual void SomeFunction() override;
virtual void SomeOtherFunction() override;
// --
- Group function overloads together
- Group by domain
Namespaces
- You can use namespaces for non-reflected types (types without code generated by Unreal Header Tool).
- Watch out for the usual namespace pitfalls
- Naming collisions
- Missing namespace for forward declarations/function definitions
- using namespace rules:
- Do not put “using” namespace declarations in the global scope, even in a .cpp file (it will cause problems with UE4s “unity” build system.)
- It is fine to put using declarations within another namespace or function body
Utility Functions
- Utility functions to be used for similar things should be grouped together
- as static class members of a class called FFooUtils
- OR as free functions in a namespace called FooUtils
- Very isolated utility functions, especially template functions that replicate the functionality of std functions can go into global namespace, e.g. from engine source code:
MoveTemp<T>()
, StaticCast<T>()
, NewObject<T>()
, etc.