Unreal has two separate pointer families. Pick by what you’re pointing at:
| Family | Use for | Types |
|---|---|---|
| Smart Pointer Library | Plain C++ classes/structs (non-UObject, usually F-prefixed) | TSharedPtr, TSharedRef, TWeakPtr, TUniquePtr |
| Object Pointers | UObjects (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.TWeakObjectPtris 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/TSharedRefto an object aTUniquePtrowns (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 nullBreaking 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. UsePin()for anything that leads to dereferencing.- Don’t use
TWeakPtras a key inTSet/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, ConstCastSharedRefThread 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
TMapkey orTSetelement. UseTObjectKeyinstead:TMap<TObjectKey<AActor>, float> DamageByActor;Pin()(returnsTStrongObjectPtr) 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. UseTWeakObjectPtr::Pin()to get aTStrongObjectPtrfirst.
Quick picker
| Situation | Use |
|---|---|
| Plain C++ object, single owner | TUniquePtr |
| Plain C++ object, shared owners, never null | TSharedRef |
| Plain C++ object, shared owners, may be null | TSharedPtr |
| Observe a plain C++ object / break a cycle | TWeakPtr |
| UObject member that should keep the target alive | UPROPERTY() TObjectPtr<T> |
| Observe a UObject that may be destroyed | TWeakObjectPtr<T> |
| Keep a UObject alive from a non-UObject class/struct | TStrongObjectPtr<T> (expensive; use sparingly) |
| Asset loaded on demand, no hard dependency | TSoftObjectPtr<T> |
| UObject as a map/set key | TObjectKey<T> |