Values, references, pointers, const, structs

Study plan 2.1 (reading C++, not writing it) · 11 min

In one breath

A value is a copy. A reference (&) is another name for something that already exists, and can't be null. A pointer (*) holds an address. It can be null and can be re-pointed. const promises "I won't change it". Engine code passes big things as const T&: no copy, no changes.

The same symbol, two jobs

In a declaration (a type)In an expression (an action)
&int& R = X; R is a reference to X&X "address of X" → a pointer
*int* P; P is a pointer to an int*P "the thing P points at" (dereference)

-> is just shorthand: P->Foo = (*P).Foo. That's why every UObject in Unreal code is used with ->: you always hold UObjects by pointer.

Passing parameters

SignatureCopies?Can change caller's?Use for
void F(FVector V)YesNoSmall types: int32, float, bool, and FVector is borderline fine
void F(const FString& S)NoNoThe default for anything bigger than a few numbers: strings, arrays, structs
void F(FHitResult& OutHit)NoYes"Out" parameters: the function fills it in. Unreal names them Out...
void F(AActor* A)Copies the address onlyCan change the actorUObjects, or anything that might be "none" (null)

WadOfGore examples: Input_Move(const FInputActionValue& InputActionValue), FindNativeInputActionByTag(const FGameplayTag& InInputTag), Debug::Print(const FString& Msg, ...).

const in the places it shows up

Const things can only call const functions

struct FWarriorInputActionConfig {
    FGameplayTag InputTag;
    UInputAction* InputAction = nullptr;
    bool IsValid() const { return InputTag.IsValid() && InputAction; }
};

for (const FWarriorInputActionConfig& Config : NativeInputActions)
    if (Config.IsValid()) { ... }       // OK only because IsValid() is const

Config is a const reference, so you may only call functions on it that have promised not to change it. Drop the const from IsValid() and that line becomes a compile error. It's the same chain that lets FindNativeInputActionByTag(...) const be called through a const UDataAsset_InputConfig*.

Habit: any member function that only reads (getters, IsValid, Find...) gets const at the end.

Pointer → bool: && InputAction works because a pointer converts to bool: nullptr is false, anything else is true.

const and pointers: read right to left

DeclarationRead it asCan change the thing?Can re-point?
UInputAction* Ppointer to UInputActionYesYes
const UInputAction* Ppointer to a const UInputActionNoYes
UInputAction* const Pconst pointer to UInputActionYesNo
const UInputAction* const Pconst pointer to a const UInputActionNoNo

The one you'll see constantly is the second: const UDataAsset_InputConfig* InInputConfig means "I'll read your data asset, not change it".

Structs vs classes

In C++ they're almost identical. Both can have functions, constructors and inheritance. The only language difference is the default access:

Members are, by default
structpublic
classprivate

So a public: inside a struct is harmless but does nothing.

The Unreal convention

UCLASS (U / A prefix)USTRUCT (F prefix)
What it isA UObjectPlain data, a value
LivesOn the heap, handled by pointerInside whatever holds it (a member, a TArray), copied around
Garbage collected?YesNo (but its UPROPERTY UObject pointers are tracked)
C++ functionsYesYes, like IsValid()
UFUNCTION (callable from Blueprint)YesNo. Use a static function in a Blueprint Function Library that takes the struct
ExamplesAWarriorHeroCharacter, UDataAsset_InputConfigFVector, FGameplayTag, FWarriorInputActionConfig

Why put a function on a struct at all? The struct answers a question about its own data, so every caller doesn't repeat InputTag.IsValid() && InputAction. If the definition of "valid" changes, it changes in one place.

Declaration vs definition, header vs cpp

Quiz yourself

In int* P = &X; what do * and & mean?

int*: P's type is "pointer to int". &X: the address of X.

Why pass FString as const FString&?

No copy (strings allocate), and const guarantees the function won't change the caller's string.

What does const after a member function's parentheses promise?

It won't modify the object's members. That's needed to call it through a const pointer or reference.

Reference vs pointer: two differences.

A reference can't be null and can't be re-seated to another object. A pointer can be null and can be re-pointed. (And you use . vs ->.)

What's wrong with for (FBigStruct S : Array) { S.Value = 0; }?

It copies each element, and sets the copy's value, so the array is unchanged. Use FBigStruct& S to modify, or const FBigStruct& to read.

You remove const from IsValid(). Why does for (const FWarriorInputActionConfig& Config : ...) Config.IsValid(); stop compiling?

Config is const, and a const object may only call const member functions. Without const, IsValid() no longer promises not to change it.

const UInputAction* P vs UInputAction* const P?

First: pointer to a const action, so you can't change the action but can re-point P. Second: const pointer, so you can change the action but can't re-point P. Read right to left.

Why does return InputTag.IsValid() && InputAction; compile when InputAction is a pointer?

Pointers convert to bool: nullptr is false, anything else is true.

What's the only language difference between struct and class?

Default access: struct members are public by default, class members private. Everything else (functions, constructors, inheritance) works in both.

Can a USTRUCT have functions? Can Blueprint call them?

It can have normal C++ functions, but not UFUNCTIONs, so Blueprint can't call them directly. Wrap them in a static function in a Blueprint Function Library.

Unreal convention: when USTRUCT (F) vs UCLASS (U/A)?

USTRUCT for plain data values that get copied and live inside something else (no GC of their own). UCLASS for UObjects: heap-allocated, held by pointer, garbage collected, can have UFUNCTIONs.