Unreal has two separate pointer families. Pick by what you’re pointing at:

FamilyUse forTypes
Smart Pointer LibraryPlain C++ classes/structs (non-UObject, usually F-prefixed)TSharedPtr, TSharedRef, TWeakPtr, TUniquePtr
Object PointersUObjects (managed by garbage collection)TObjectPtr, TWeakObjectPtr, TSoftObjectPtr, TStrongObjectPtr

Don't mix the families Smart Pointer Library types can't hold UObjects. UObjects are memory-managed by the garbage collector, and the two systems have no overlap. TWeakObjectPtr is not an exception to this rule; it belongs to the separate Object Pointer family.

Don't mix unique and shared ownership Never make a TSharedPtr/TSharedRef to an object a TUniquePtr owns (or the reverse). The unique pointer will still delete the object when it dies, leaving the shared pointers dangling.

More info: Smart Pointers · Object Pointers


Sample type used below

#include "Templates/SharedPointer.h" // TSharedPtr, TSharedRef, TWeakPtr, TSharedFromThis
#include "Templates/UniquePtr.h"     // TUniquePtr, MakeUnique
 
struct FPlayerStats
{
    int32 Score = 0;
 
    explicit FPlayerStats(int32 InScore) : Score(InScore) {}
    void AddScore(int32 Amount) { Score += Amount; }
};

1. TSharedPtr

Owns the object and keeps it alive while any TSharedPtr/TSharedRef references it. Can be null.

TSharedPtr<FPlayerStats> Empty;                                  // null
TSharedPtr<FPlayerStats> Stats = MakeShared<FPlayerStats>(100);  // object + ref controller in ONE allocation
TSharedPtr<FPlayerStats> Copy  = Stats;                          // ref count +1
 
// Null-check before use (all three are equivalent)
if (Stats.IsValid()) { Stats->AddScore(10); }
if (Stats)           { Stats->AddScore(10); }
if (Stats.Get() != nullptr) { Stats->AddScore(10); }
 
// Move: transfers ownership, Copy becomes null, count unchanged
TSharedPtr<FPlayerStats> Moved = MoveTemp(Copy);
 
// Release (object is deleted when the LAST owner lets go)
Stats.Reset();
Moved = nullptr;
  • MakeShared = one allocation, needs a public constructor.
  • MakeShareable(new T(...)) = two allocations, but works with private constructors, objects you didn’t create, or custom deleters.

2. TSharedRef

Same ownership as TSharedPtr, but can never be null: it must be initialized with a valid object. Epic prefers it over TSharedPtr unless you actually need null.

TSharedRef<FPlayerStats> StatsRef = MakeShared<FPlayerStats>(50); // must be initialized
StatsRef->AddScore(5);                                            // no null check needed
 
// Ref → Ptr: implicit, always valid
TSharedPtr<FPlayerStats> AsPtr = StatsRef;
 
// Ptr → Ref: ToSharedRef() ASSERTS if the pointer is null, so check first
if (AsPtr.IsValid())
{
    TSharedRef<FPlayerStats> BackToRef = AsPtr.ToSharedRef();
}

Good fit for factory functions that always succeed:

TSharedRef<FPlayerStats> CreateStats() { return MakeShared<FPlayerStats>(0); }

3. TWeakPtr

Observes an object without owning it, so it doesn’t affect lifetime. Use Pin() to get a temporary TSharedPtr before accessing.

TSharedPtr<FPlayerStats> Owner = MakeShared<FPlayerStats>(0);
TWeakPtr<FPlayerStats> Observer = Owner;                 // does NOT keep it alive
 
if (TSharedPtr<FPlayerStats> Pinned = Observer.Pin())    // keeps it alive for this scope
{
    Pinned->AddScore(1);
}
 
Owner.Reset();          // last owner gone → object deleted
// Observer.Pin() now returns null

Breaking reference cycles: parent owns children strongly, children point back weakly.

struct FTreeNode
{
    TArray<TSharedPtr<FTreeNode>> Children; // strong: parent owns children
    TWeakPtr<FTreeNode> Parent;             // weak: no cycle → no leak
};

TWeakPtr gotchas

  • IsValid() only tells you about right now. With thread-safe pointers the object can die on another thread immediately after. Use Pin() for anything that leads to dereferencing.
  • Don’t use TWeakPtr as a key in TSet/TMap; it can go invalid without notifying the container. Fine as a value.

4. TUniquePtr

Sole owner. Can’t be copied (compile error), only moved. Deletes the object when it goes out of scope. Same size as a raw pointer.

TUniquePtr<FPlayerStats> Unique = MakeUnique<FPlayerStats>(10);
 
// TUniquePtr<FPlayerStats> Copy = Unique;            // ❌ compile error: can't copy
 
TUniquePtr<FPlayerStats> NewOwner = MoveTemp(Unique); // ✅ transfer ownership; Unique is now null
 
if (NewOwner)
{
    NewOwner->AddScore(1);
}
 
NewOwner = nullptr; // deletes now (would also delete automatically at end of scope)

Typical use: an actor owning a plain C++ helper (no UPROPERTY needed, it’s not a UObject):

// AMyActor.h
TUniquePtr<FPlayerStats> LocalStats;
 
// AMyActor.cpp
LocalStats = MakeUnique<FPlayerStats>(0);

Extras (Smart Pointer Library)

TSharedFromThis: get a shared ref to this

class FQuest : public TSharedFromThis<FQuest>
{
public:
    void RegisterIn(TArray<TSharedRef<FQuest>>& QuestLog)
    {
        QuestLog.Add(AsShared()); // TSharedRef<FQuest> to this object
    }
};
 
TSharedRef<FQuest> Quest = MakeShared<FQuest>();
TArray<TSharedRef<FQuest>> QuestLog;
Quest->RegisterIn(QuestLog);

Never call AsShared() / SharedThis(this) from a constructor. The shared reference isn't set up yet, so it crashes or asserts.

AsShared() returns the type given to TSharedFromThis<> (may be a base class); SharedThis(this) returns the actual derived type.

Casting (no dynamic cast, no RTTI)

struct FItem   { virtual ~FItem() = default; };
struct FWeapon : FItem { int32 Damage = 10; };
 
TSharedPtr<FItem> Item = MakeShared<FWeapon>();                   // upcast: implicit
TSharedPtr<FWeapon> Weapon = StaticCastSharedPtr<FWeapon>(Item);  // downcast: YOU must know it's an FWeapon
// Also: StaticCastSharedRef, ConstCastSharedPtr, ConstCastSharedRef

Thread safety

// UE5 default is ESPMode::ThreadSafe (atomic, lockless ref counting)
TSharedPtr<FPlayerStats> SafeByDefault = MakeShared<FPlayerStats>(0);
 
// Opt out for speed when only ONE thread ever touches it
TSharedPtr<FPlayerStats, ESPMode::NotThreadSafe> Fast =
    MakeShared<FPlayerStats, ESPMode::NotThreadSafe>(0);

Epic's Smart Pointers overview page still says pointers are single-threaded by default. That's out of date: the UE5 API reference says thread safety is on by default ( ESPMode::ThreadSafe). Reads/copies are thread-safe; writes/resets still need synchronization.

Passing to functions

void PrintScore(const FPlayerStats& Stats);           // ✅ preferred
void PrintScore(TSharedPtr<FPlayerStats> Stats);      // ❌ ref-count overhead for nothing
 
PrintScore(*SafeByDefault);

5. TWeakObjectPtr (Object Pointer family)

Weak reference to a UObject. Doesn’t prevent garbage collection; becomes null automatically when the object is destroyed or collected. UPROPERTY is optional but supported.

// AMyTurret.h
UPROPERTY()
TWeakObjectPtr<AActor> CurrentTarget;
 
// AMyTurret.cpp
void AMyTurret::SetTarget(AActor* NewTarget)
{
    CurrentTarget = NewTarget;                 // does NOT keep the target alive
}
 
void AMyTurret::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);
 
    if (AActor* Target = CurrentTarget.Get())  // resolve ONCE per function; nullptr if gone
    {
        AimAt(Target->GetActorLocation());
    }
}

Capturing in a lambda (Epic’s pattern; the lambda may run after the object is destroyed):

#include "UObject/StrongObjectPtr.h"
 
FSimpleDelegate OnReloaded;
OnReloaded.BindLambda([WeakTurret = MakeWeakObjectPtr(this)]()
{
    if (TStrongObjectPtr<AMyTurret> Turret = WeakTurret.Pin()) // keeps it alive while in scope
    {
        Turret->Fire();
    }
});

TWeakObjectPtr gotchas

  • Not supported as a TMap key or TSet element. Use TObjectKey instead: TMap<TObjectKey<AActor>, float> DamageByActor;
  • Pin() (returns TStrongObjectPtr) is in the UE 5.5+ API reference but not the 5.3 one. On older versions use .Get() + null check.
  • For worker threads: don’t touch UObjects through raw/TObjectPtr. Use TWeakObjectPtr::Pin() to get a TStrongObjectPtr first.

Quick picker

SituationUse
Plain C++ object, single ownerTUniquePtr
Plain C++ object, shared owners, never nullTSharedRef
Plain C++ object, shared owners, may be nullTSharedPtr
Observe a plain C++ object / break a cycleTWeakPtr
UObject member that should keep the target aliveUPROPERTY() TObjectPtr<T>
Observe a UObject that may be destroyedTWeakObjectPtr<T>
Keep a UObject alive from a non-UObject class/structTStrongObjectPtr<T> (expensive; use sparingly)
Asset loaded on demand, no hard dependencyTSoftObjectPtr<T>
UObject as a map/set keyTObjectKey<T>

Sources