Skip to main content

07 / 16

Ownership and loading

Pick the pointer that says whether it retains, observes, or defers loading.

Unreal's garbage collector manages UObjects. Plain C++ objects use ordinary C++ ownership. The pointer type is how you tell the two apart.

Rules

  1. 01Every reflected UObject reference keeps that object alive. A cache that must not extend an object lifetime needs TWeakObjectPtr, not a reflected hard reference.
  2. 02Use IsValid() rather than a null test on a raw UObject pointer. It reports non-null, reachable, and not pending kill or garbage, which a null check alone misses.
  3. 03A TSoftObjectPtr costs nothing until it resolves. Calling LoadSynchronous during gameplay trades that away for a hitch, so load through FStreamableManager and handle the pending state.
  4. 04Keep the FStreamableHandle an async load returns. It is how you query completion, bind a completion delegate, and cancel the request.
  5. 05A hard reference in a header pulls the referenced asset into memory with the owner. Soft references are what keep an optional asset out of the initial load.

Pointer types

T*

A local or parameter only uses the UObject for a short time. Has no wrapper or tracking overhead.

CostsDoes not keep the object alive or clear itself when the object is destroyed.

TObjectPtr<T>

A reflected field keeps, serializes, or replicates a UObject reference. Unreal tracks it for garbage collection when it is marked UPROPERTY.

CostsThe hard reference may keep an object or asset loaded.

TWeakObjectPtr<T>

A cache or observer must not keep a UObject alive. Clears when the object is destroyed.

CostsEvery use needs a validity check or a pinned reference.

TSoftObjectPtr<T>

An asset should load on demand instead of becoming a hard dependency. Stores an asset path that Unreal can serialize.

CostsLoading is asynchronous, or it blocks when forced to load synchronously.

TStrongObjectPtr<T>

A non-UObject owner must keep a UObject alive. Keeps the object alive without a UPROPERTY field.

CostsTracking costs more than a raw pointer and can make retained objects harder to trace.

TUniquePtr<T> / TSharedPtr<T>

A plain C++ object needs exclusive or shared ownership. Makes C++ ownership independent of UObject garbage collection.

CostsShared ownership adds reference-counting overhead. Neither pointer type manages UObjects.

Make ownership clear from the pointer type

The pointer tells you whether the field keeps an object alive, observes it, loads it later, or owns a plain C++ object.

CombatController.hcpp
UPROPERTY(VisibleAnywhere, Category = "Combat")
TObjectPtr<UWeaponComponent> WeaponComponent;

TWeakObjectPtr<AActor> CurrentTarget;

UPROPERTY(EditDefaultsOnly, Category = "UI")
TSoftObjectPtr<UTexture2D> InventoryIcon;

TUniquePtr<FNavigationQuery> PendingQuery;

TSharedPtr<FInventoryViewModel> InventoryViewModel;
Why
Unreal garbage collection and C++ reference counting can follow the intended lifetime. Other developers do not have to inspect every assignment to work out who owns what.
Costs
A strong reference can keep an object or asset alive longer than expected. Weak and soft references require a validity check or load step when used.
Instead
Use a raw UObject* for short-lived local variables and parameters. Use TStrongObjectPtr cautiously when a non-UObject owner must keep a UObject alive.

Gotcha

TObjectPtr does not add garbage collection safety. UPROPERTY does

Epic's own header states that once resolved, a TObjectPtr participates in garbage collection identically to a raw pointer. Reachability comes from the UPROPERTY macro, which is what exposes the field to the reflection system and the collector. What TObjectPtr adds is editor-build access tracking, optional lazy loading, and cook-time dependency tracking. An unreflected TObjectPtr member is exactly as unsafe as an unreflected raw pointer.

Soft-load optional assets

The inventory icon loads when the UI asks for it instead of becoming a hard dependency of the class.

ItemTile.cppcpp
void UItemTile::LoadIcon()
{
    if (InventoryIcon.IsNull())
    {
        ClearIcon();
        return;
    }

    FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
    IconLoadHandle = Streamable.RequestAsyncLoad(
        InventoryIcon.ToSoftObjectPath(),
        FStreamableDelegate::CreateWeakLambda(this, [this]()
        {
            SetIcon(InventoryIcon.Get());
        }));
}
Why
Loading the class does not pull the texture into memory. The weak callback also prevents the request from calling into a destroyed widget.
Costs
The texture is not ready immediately, so the UI needs a placeholder. The callback will not run if the widget is destroyed first.
Instead
Use TObjectPtr when the asset always belongs in memory with the object. Asset Manager bundles work better when several soft references need to load as one group.

The Smart Pointers & References page covers serialization, networking, and garbage collection behavior for each pointer type.

All 16 rules