Values, references, pointers, const, structs
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
| Signature | Copies? | Can change caller's? | Use for |
|---|---|---|---|
void F(FVector V) | Yes | No | Small types: int32, float, bool, and FVector is borderline fine |
void F(const FString& S) | No | No | The default for anything bigger than a few numbers: strings, arrays, structs |
void F(FHitResult& OutHit) | No | Yes | "Out" parameters: the function fills it in. Unreal names them Out... |
void F(AActor* A) | Copies the address only | Can change the actor | UObjects, 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 FVector ForwardDirection = ...;: a local that won't change. It makes the code easier to read ("this is set once") and the compiler stops you reassigning it by mistake.UInputAction* FindNativeInputActionByTag(...) const;: a const member function. It promises not to modify the object it's called on. That's what lets you call it through aconst UDataAsset_InputConfig*, which is whatBindNativeInputActionreceives.for (const FWarriorInputActionConfig& Config : NativeInputActions): loop by const reference, so no copy per element. Writingfor (FWarriorInputActionConfig Config : ...)copies every element. It still works, but slower, and edits toConfigwould change nothing.
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
| Declaration | Read it as | Can change the thing? | Can re-point? |
|---|---|---|---|
UInputAction* P | pointer to UInputAction | Yes | Yes |
const UInputAction* P | pointer to a const UInputAction | No | Yes |
UInputAction* const P | const pointer to UInputAction | Yes | No |
const UInputAction* const P | const pointer to a const UInputAction | No | No |
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 | |
|---|---|
struct | public |
class | private |
So a public: inside a struct is harmless but does nothing.
The Unreal convention
UCLASS (U / A prefix) | USTRUCT (F prefix) | |
|---|---|---|
| What it is | A UObject | Plain data, a value |
| Lives | On the heap, handled by pointer | Inside whatever holds it (a member, a TArray), copied around |
| Garbage collected? | Yes | No (but its UPROPERTY UObject pointers are tracked) |
| C++ functions | Yes | Yes, like IsValid() |
UFUNCTION (callable from Blueprint) | Yes | No. Use a static function in a Blueprint Function Library that takes the struct |
| Examples | AWarriorHeroCharacter, UDataAsset_InputConfig | FVector, 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
- Declaration: "this exists, here's its shape". Goes in the
.hso others can use it. - Definition: "here's the actual code / the actual storage". Goes in a
.cpp, once. - Exceptions that live in headers: templates,
inline/FORCEINLINEfunctions (the getters), and class bodies.
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.