Skip to main content

05 / 16

Const correctness and moves

Carry const through the whole read path. Move values the function takes ownership of.

Const tells the compiler and the caller that a value will not change. It only works when it covers the whole path: the method, the parameters, the locals, and the loop variable.

Rules

  1. 01Mark a member function const when it does not modify observable state.
  2. 02Use const T& for a required read-only reference and const T* for a nullable read-only pointer.
  3. 03Prefer const locals when they do not change after initialization.
  4. 04Never add const to a value return type. It inhibits move behavior without protecting caller state.
  5. 05Never call MoveTemp on a const value. Moving normally needs permission to modify the source.
  6. 06MoveTemp static-asserts on both a const object and an rvalue. Epic made both a compile error on purpose, because a move that silently degrades into a copy is the bug you most want reported.
  7. 07Use MoveTempIfPossible in a template or macro where the argument may legitimately be const or already an rvalue. It performs the same cast without the static assertions.
  8. 08Const on a member function is a promise about observable state, not about bits. Use mutable for a cache or lazily computed value that does not change what callers can see.
  9. 09Do not const_cast constness away to satisfy an older API. Fix the declaration instead, because writing through the cast is undefined behavior when the object is genuinely const.

Keep the entire read path const

The function, loop value, pointer target, and calculated value all state that they will not change.

InventoryComponent.cppcpp
float UInventoryComponent::GetTotalWeight() const
{
    float TotalWeight = 0.0f;

    for (const FInventoryEntry& Entry : Entries)
    {
        const UItemDefinition* Definition = Entry.ItemDefinition.Get();
        if (Definition == nullptr)
        {
            continue;
        }

        const float EntryWeight = Definition->GetWeight() * Entry.Quantity;
        TotalWeight += EntryWeight;
    }

    return TotalWeight;
}
Why
The compiler catches accidental writes, and callers know the query is safe to use on a const object.
Costs
Adding const to an older API can uncover methods that should have been const all along. Fix those declarations instead of casting const away.
Instead
Return a copy when the caller needs independent ownership. Return a const reference only when the owner outlives every caller that retains it.

Take a value when the function keeps it

The setter accepts an lvalue or temporary, then moves its local value into the member.

InventorySnapshot.cppcpp
void FInventorySnapshot::SetEntries(TArray<FInventoryEntry> InEntries)
{
    Entries = MoveTemp(InEntries);
}

TArray<FInventoryEntry> FInventorySnapshot::ReleaseEntries()
{
    return MoveTemp(Entries);
}
Why
One overload handles both cases. A temporary moves into storage, while an lvalue makes the copy its caller requested.
Costs
An lvalue is copied before entering the function. A const-reference overload can avoid that copy if the function often rejects the value before storing it.
Instead
Use const TArray<T>& when the function only reads. Use TArray<T>&& when accepting lvalues would be a caller error and the ownership transfer must be explicit.

All 16 rules