| name | cpp-fundamentals |
| description | 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. |
| metadata | {"engine-version":"5.7","category":"cpp-foundations"} |
Unreal Engine C++ fundamentals
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.
- Diagnosing UHT errors ("Unrecognized type", "missing
generated.h", "GENERATED_BODY missing").
- 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
AActor, UActorComponent, etc.).
- 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
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS(Blueprintable, BlueprintType)
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY()
public:
AMyActor();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
float Health = 100.f;
UFUNCTION(BlueprintCallable, Category = "Stats")
void ApplyDamage(float Amount);
protected:
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere)
TObjectPtr<USceneComponent> Root;
};
#include "MyActor.h"
AMyActor::AMyActor()
{
PrimaryActorTick.bCanEverTick = true;
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
}
void AMyActor::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.
- Always call
Super:: in overridden engine virtuals (BeginPlay, Tick, EndPlay, …).
UCLASS specifiers
Verified in ObjectMacros.h (namespace UC, line 792):
Blueprintable — the class can be subclassed in Blueprint.
BlueprintType — can be used as a Blueprint variable type.
Abstract — cannot be instantiated directly.
Config=Game — properties marked Config are read/written from *.ini files.
MinimalAPI — exports only the reflection boilerplate (Cast<>, etc.); use in leaf-module classes
to reduce link surface.
meta=(PrioritizeCategories="Stats") — editor ordering hints.
UPROPERTY specifiers
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")
void OnSpotted(AActor* By);
void OnSpotted_Implementation(AActor* By);
USTRUCT, UENUM, UINTERFACE
USTRUCT(BlueprintType)
struct FLoadout
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Loadout")
int32 Ammo = 0;
};
UENUM(BlueprintType)
enum class EDoorState : uint8 { Closed, Opening, Open, Closing };
UINTERFACE(MinimalAPI, Blueprintable)
class UInteractable : public UInterface { GENERATED_BODY() };
class IInteractable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, Category="Interaction")
void Interact(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.
See references/reflection-macros.md for the full specifier
reference with less-common options.
Object construction
| API | When | Notes |
|---|
CreateDefaultSubobject<T>(TEXT("Name")) | Constructor only | 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:147). NewObject template
overloads are in UObjectGlobals.h (lines 1891, 1919, 1934).
The CDO is created by UClass::GetDefaultObject (Class.h:4373) the first time it is needed.
After construction the CDO is read-only; modify only through archetype/default propagation.
Full construction paths, CDO lifecycle, and PostInitProperties are in
references/uobject-lifecycle-and-cdo.md.
Pointer and ownership cheat sheet
| Need | Use |
|---|
| UObject member you own / keep alive | UPROPERTY() TObjectPtr<UType> |
| Local variable or function parameter | raw UType* |
| Non-owning ref that may be destroyed | 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:487) 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:1896).
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:3926).
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.7, under Engine/Source/Runtime/CoreUObject/Public/UObject/):
Object.h:94 — UObject class definition; :147 — CreateDefaultSubobject; :222 —
PostInitProperties; :366 — BeginDestroy; :373 — IsReadyForFinishDestroy; :387 —
FinishDestroy; :1875 — IsValid().
ObjectMacros.h:744 — UPROPERTY/UFUNCTION/USTRUCT/UENUM macro stubs; :765 —
GENERATED_BODY; :792 — UCLASS specifiers; :945 — UFUNCTION specifiers; :1046 —
UPROPERTY specifiers; :1176 — USTRUCT specifiers.
Class.h:476 — UStruct; :1719 — UScriptStruct; :3792 — UClass; :3926 —
deprecated ClassDefaultObject field (5.6+); :4373 — GetDefaultObject().
UObjectGlobals.h:1891 — NewObject<T>(Outer, Class, Name, …); :1919 —
NewObject<T>(Outer) (no name); :1934 — NewObject<T>(Outer, Name, …).
ObjectPtr.h:487 — TObjectPtr<T>.
Interface.h:18 — UInterface base class.
Official docs (UE 5.7):
Deep-dive references in this skill:
Cross-reference sibling skills:
memory-and-gc — deep dive on pointer types, weak pointers, GC tuning.
actors-and-components — Actor/component lifecycle, spawning, ticking.
blueprint-cpp-integration — exposing C++ types to Blueprint in depth.
module-and-build-system — the module API macro, Build.cs, UHT pipeline details.