SuperAPI AI Directory

cpp-fundamentals

Coding & development

View repository

Updated 11-06 16:21

About this skill

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:

  1. 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.).
  2. 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.
  3. 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 include

UCLASS(Blueprintable, BlueprintType)
class MYGAME_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")
    void ApplyDamage(float Amount);

protected:
    virtual void BeginPlay() 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);
}

void AMyActor::BeginPlay()
{
    Super::BeginPlay();   // call Super on all engine virtual overrides
}

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);   // C++ default, override in BP

USTRUCT, UENUM, UINTERFACE

// Value type struct — no GC, stack/value semantics
USTRUCT(BlueprintType)
struct FLoadout
{
    GENERATED_BODY()
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Loadout")
    int32 Ammo = 0;
};

// Enum — uint8-backed required for Blueprint use
UENUM(BlueprintType)
enum class EDoorState : uint8 { Closed, Opening, Open, Closing };

// Interface: always declare both halves (U* is the UObject shell; I* is the C++ interface)
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: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.

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: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/):

  • Object.h:98 — UObject class definition; :151 — CreateDefaultSubobject; :226 — PostInitProperties; :361 — BeginDestroy; :368 — IsReadyForFinishDestroy; :382 — FinishDestroy; :1886 — IsValid().
  • ObjectMacros.h:778 — UPROPERTY/UFUNCTION/USTRUCT/UENUM macro stubs; :800 — GENERATED_BODY; :832 — UCLASS specifiers; :985 — UFUNCTION specifiers; :1086 — UPROPERTY specifiers; :1216 — USTRUCT specifiers.
  • Class.h:495 — UStruct; :1774 — UScriptStruct; :3893 — UClass; :4045 — deprecated ClassDefaultObject field (5.6+); :4519 — GetDefaultObject().
  • UObjectGlobals.h:1931 — NewObject<T>(Outer, Class, Name, …); :1959 — NewObject<T>(Outer) (no name); :1974 — NewObject<T>(Outer, Name, …).
  • ObjectPtr.h:519 — TObjectPtr<T>.
  • Interface.h:18 — UInterface base class.

Official docs (UE 5.8):

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.