Skip to main content

10 / 16

Modern C++ and containers

Use modern C++ where it reads better. Keep Unreal containers in engine-facing code.

Unreal Engine supports modern C++, but Epic still recommends explicit types and Unreal containers in most engine-facing code. They inspect better in a debugger and work directly with Unreal APIs.

Rules

  1. 01Use override for every override and final when further overrides are unsupported.
  2. 02Use static_assert for a compile-time invariant.
  3. 03Use enum class instead of unscoped enums.
  4. 04Prefer range loops or a named Algo operation over manual iterators.
  5. 05Return Unreal containers and strings by value when that produces the cleanest API. Their move operations avoid the old temporary-copy penalty.
  6. 06Use explicitly sized integers for serialized, replicated, or binary-format data.
  7. 07Use std::atomic and standard type traits in new code. Keep standard containers and strings at interop boundaries rather than mixing them into an Unreal-facing API.
  8. 08Keep lambdas short enough that the surrounding operation remains readable.
  9. 09Test bit flags with EnumHasAnyFlags and EnumHasAllFlags rather than a raw bitwise &. They read as the question being asked and avoid an accidental narrowing conversion.
  10. 10Use MoveTempIfPossible in templates and macros where the argument may legitimately be const or an rvalue. MoveTemp static-asserts in both cases by design.
  11. 11Call Reserve before filling a TArray whose final size you already know. Each growth reallocates the buffer and moves every element into it.
  12. 12Use Emplace when constructing an element in place. Add builds a temporary first and then moves it, which is wasted work for anything larger than a scalar.
  13. 13Iterate with const auto& rather than auto when the loop only reads. Plain auto copies each element, which is invisible at the call site and expensive for a struct.

Type declarations

Explicit type

The type is readable and helps explain the value. Still makes sense in a diff or code review without IDE hints.

CostsIterator and template types can become too long to help.

auto

The value is a lambda, a long iterator type, or a difficult template expression. Removes a declaration that would be harder to read than the operation.

CostsCan hide copies, constness, pointer levels, or a surprising return type.

Member defaults

Default member initializer

A game type has a simple default that belongs next to the field. Keeps the declaration and default together.

CostsChanging the header rebuilds its dependents, and some values need a complete type.

Constructor initializer

Initialization depends on another value, a base class, or a type defined in the source file. Keeps that implementation out of the public header.

CostsDefaults may end up split between the header and source file.

Containers

Unreal container

The data is used by gameplay code, reflection, serialization, or engine APIs. Works directly with Unreal allocators, APIs, and serialization.

CostsLess portable outside Unreal.

Standard container

A standard or third-party API already uses that container. Avoids converting the data at the boundary.

CostsEpic advises against standard containers and strings in ordinary engine-facing code.

Use range loops without hiding useful types

A const reference avoids copying each pair and keeps the key and value types visible.

InventoryDebug.cppcpp
for (const TPair<FGameplayTag, int32>& Stack : StackCounts)
{
    if (Stack.Value <= 0)
    {
        continue;
    }

    UE_LOG(
        LogInventory,
        Verbose,
        TEXT("%s: %d"),
        *Stack.Key.ToString(),
        Stack.Value);
}
Why
The loop cannot modify the map. The explicit type also remains clear in a diff or code review where IDE type hints are unavailable.
Costs
Some iterator and template types are unreadably long. Epic permits auto for those cases and for lambdas.
Instead
Use Algo functions when a named operation such as Sort, Transform, or FindByPredicate says more than the loop mechanics.

All 16 rules