Write correct Unreal Engine C++ using the UObject reflection system — UCLASS/USTRUCT/ UENUM/UINTERFACE macros, UPROPERTY and UFUNCTION specifiers, GENERATED_BODY, the *.generated.h pipeline, class prefixes (U/A/F/E/I), module API export macros, the Class Default Object (CDO), NewObject vs CreateDefaultSubobject, garbage-collection-safe ownership, and UClass vs UScriptStruct internals. Use when authoring or editing any UE C++ class, exposing members or functions to Blueprints or replication, fixing UHT/reflection build errors, or choosing between pointer and ownership types.
Write correct Unreal Engine C++ using the UObject reflection system — UCLASS/USTRUCT/ UENUM/UINTERFACE macros, UPROPERTY and UFUNCTION specifiers, GENERATED_BODY, the *.generated.h pipeline, class prefixes (U/A/F/E/I), module API export macros, the Class Default Object (CDO), NewObject vs CreateDefaultSubobject, garbage-collection-safe ownership, and UClass vs UScriptStruct internals. Use when authoring or editing any UE C++ class, exposing members or functions to Blueprints or replication, fixing UHT/reflection build errors, or choosing between pointer and ownership types.
Unreal C++ is standard C++ augmented by a reflection layer generated by the Unreal Header Tool
(UHT). Getting the reflection macros right determines whether your code compiles, appears in
Blueprints, replicates correctly, and survives garbage collection.
When to use this skill
Creating or editing any U*/A* class or F* struct in C++.
Exposing a member or function to Blueprints, the editor, or network replication.
Deciding between TObjectPtr, raw pointer, TWeakObjectPtr, or TSharedPtr.
Understanding how the CDO works or why a constructor runs in the editor.
Mental model
Three interlocking pieces:
UObject is the base of the reflected, garbage-collected object graph. Every class that opts
into the system derives from it (directly or via , , etc.).
AActor
UActorComponent
UHT parses your headers before the compiler and emits a <ClassName>.generated.h that
contains boilerplate for reflection, serialization, and Blueprint integration. You include this
file last and place GENERATED_BODY() inside the type.
UClass (the runtime type descriptor) holds a Class Default Object (CDO) — one instance
constructed at startup with all defaults applied. New instances copy from the CDO. The
constructor runs on the CDO and in the editor; never put gameplay logic there.
UObjects are garbage-collected. Any UObject* you want kept alive must be stored in a UPROPERTY.
A plain C++ pointer is invisible to the GC and will dangle after the next collection.
Class prefixes (mandatory)
Prefix
Meaning
Examples
U
UObject-derived (non-Actor)
UActorComponent, UMyDataAsset
A
Actor-derived (world-placeable)
AActor, AMyCharacter
F
Plain struct / non-UObject value type
FVector, FMyConfig
E
Enum
EMyState
I
Interface (the IFoo half of a UFoo/IFoo pair)
IInteractable
T
Template
TArray, TObjectPtr
S
Slate widget
SButton
The class name after the prefix must match the filename (minus prefix): AMyPawn lives in
MyPawn.h / MyPawn.cpp.
Anatomy of a UObject class
// MyActor.h#pragma once#include"CoreMinimal.h"#include"GameFramework/Actor.h"#include"MyActor.generated.h"// ALWAYS LAST includeUCLASS(Blueprintable, BlueprintType)
classMYGAME_API AMyActor : public AActor // MYGAME_API = module export macro
{
GENERATED_BODY()
public:
AMyActor();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
float Health = 100.f;
UFUNCTION(BlueprintCallable, Category = "Stats")
voidApplyDamage(float Amount);
protected:
virtualvoidBeginPlay()override;
UPROPERTY(VisibleAnywhere)
TObjectPtr<USceneComponent> Root; // UPROPERTY keeps it alive in the GC graph
};
// MyActor.cpp#include"MyActor.h"
AMyActor::AMyActor()
{
PrimaryActorTick.bCanEverTick = true;
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
}
voidAMyActor::BeginPlay(){
Super::BeginPlay(); // call Super on all engine virtual overrides
}
voidAMyActor::ApplyDamage(float Amount){
Health = FMath::Max(0.f, Health - Amount);
}
Four rules that prevent most build breaks:
#include "X.generated.h" is the last include in the header.
Every reflected class/struct has GENERATED_BODY().
The class is annotated with the module API macro (MYGAME_API) so other modules can link it.
Verified in ObjectMacros.h (namespace UP, line 1046):
Edit / visibility (Details panel):
EditAnywhere — editable on CDO and placed instances.
EditDefaultsOnly — CDO (Blueprint defaults) only.
EditInstanceOnly — placed instances only.
VisibleAnywhere / VisibleDefaultsOnly / VisibleInstanceOnly — read-only in the editor.
Blueprint access:
BlueprintReadWrite — get and set in BP. BlueprintReadOnly — get only.
Common combos and meta:
Category = "Group" — organizes the Details panel (required for every exposed member).
meta=(ClampMin="0", ClampMax="100") — value ranges.
meta=(AllowPrivateAccess="true") — expose a private member to Blueprint.
Transient — not saved to disk. SaveGame — included in SaveGame serialization.
Replicated / ReplicatedUsing=OnRep_Func — network replication (see networking-and-replication).
Instanced — for per-instance configurable subobjects.
Memory rule: any UObject* member you want kept alive must be a UPROPERTY. Use
TObjectPtr<UType> for class members (editor-aware, access-tracked in UE5); raw UType* is
acceptable for local variables and function parameters. Without UPROPERTY, the GC can collect the
object and leave a dangling pointer. See memory-and-gc for the full pointer hierarchy.
UFUNCTION specifiers
Verified in ObjectMacros.h (namespace UF, line 945):
BlueprintCallable — callable from Blueprint event graphs.
BlueprintPure — no side effects; pure node (no exec pins).
BlueprintImplementableEvent — declared in C++, fully implemented in Blueprint; no C++ body.
BlueprintNativeEvent — C++ default (Func_Implementation) overridable in Blueprint.
CallInEditor — adds a button in the Details panel for editor-time execution.
Exec — registers as a console command.
Server / Client / NetMulticast + Reliable/Unreliable — RPCs.
BlueprintNativeEvent pattern:
UFUNCTION(BlueprintNativeEvent, Category="AI")
voidOnSpotted(AActor* By);
voidOnSpotted_Implementation(AActor* By); // C++ default, override in BP
USTRUCT, UENUM, UINTERFACE
// Value type struct — no GC, stack/value semanticsUSTRUCT(BlueprintType)
structFLoadout
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Loadout")
int32 Ammo = 0;
};
// Enum — uint8-backed required for Blueprint useUENUM(BlueprintType)
enum classEDoorState : uint8 { Closed, Opening, Open, Closing };
// Interface: always declare both halves (U* is the UObject shell; I* is the C++ interface)UINTERFACE(MinimalAPI, Blueprintable)
classUInteractable : public UInterface { GENERATED_BODY() };
classIInteractable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, Category="Interaction")
voidInteract(AActor* Instigator);
};
Key differences between USTRUCT and UCLASS:
UScriptStruct (the runtime descriptor for USTRUCT) derives from UStruct, not UClass.
Struct instances are value types — no GC, no CDO, no NewObject.
UPROPERTY inside a struct still enables serialization and editor exposure; it does not imply
GC ownership (there is nothing for the GC to track in a value type).
Prefer USTRUCT for lightweight data bags (stats, configs, hit results). Use UCLASS when you
need GC lifetime, Blueprint subclassing, or per-instance identity.
Creates owned subobject; names must be unique per class
NewObject<T>(Outer)
Runtime, non-Actor
Use for any UObject that is not an Actor
GetWorld()->SpawnActor<T>(Class, FTransform)
Runtime, Actors
See actors-and-components
Cast<T>(Obj)
Anywhere
Safe checked cast; returns null on failure — never static_cast
IsValid(Obj)
Anywhere
True if non-null and not garbage; prefer over bare null check
CreateDefaultSubobject is declared on UObject (Object.h:151). NewObject template
overloads are in UObjectGlobals.h (lines 1931, 1959, 1974).
The CDO is created by UClass::GetDefaultObject (Class.h:4519) the first time it is needed.
After construction the CDO is read-only; modify only through archetype/default propagation.
TWeakObjectPtr<UType> — check .IsValid() before use
Non-UObject heap object, shared ownership
TSharedPtr<T> / TSharedRef<T>
Asset reference loaded on demand
TSoftObjectPtr<T> / TSoftClassPtr<T>
TObjectPtr<T> (declared in ObjectPtr.h:519) is the modern form for UPROPERTY members in UE5.
Older code uses raw T* UPROPERTY() pointers, which still compile and work. Legacy code you
encounter will mix both styles.
Gotchas
Forgot UPROPERTY on a UObject member* → random crashes after GC. The most common bug.
generated.h not last / missing GENERATED_BODY() → cryptic UHT errors that appear
unrelated to the actual mistake.
Gameplay logic in the constructor → runs on the CDO and in the editor; no world, no
gameplay. Always put gameplay init in BeginPlay.
CreateDefaultSubobject outside the constructor → asserts (detected by
FObjectInitializer::AssertIfInConstructor, UObjectGlobals.h:1936).
NewObject with an empty name inside a constructor → also asserts; use
CreateDefaultSubobject instead.
Calling editor-only API from runtime code → packaging failures; guard with #if WITH_EDITOR.
UENUM not backed by uint8 → Blueprint-unusable; BlueprintType enum must be uint8.
ClassDefaultObject accessed directly → deprecated as of UE 5.6; use
GetDefaultObject()/GetDefault<T>() instead (Class.h:4045).
FName/FString/FText mix-ups → see core-types-and-containers.
Version notes
TObjectPtr<T> (UPROPERTY member idiom) and TObjectPtr-backed GC barrier were introduced in
UE5. Pre-5.0 code uses raw T* UPROPERTY(), which still works but lacks editor access tracking.
MarkPendingKill() was replaced by MarkAsGarbage() in UE5; with gc.PendingKillEnabled=false
(the new default), the GC no longer auto-nulls pointers — use IsValid() and clear references
in EndPlay/callbacks.
ClassDefaultObject was deprecated as a direct field access in 5.6; use GetDefaultObject().
References & source material
Engine source (UE 5.8, under Engine/Source/Runtime/CoreUObject/Public/UObject/):