UObject
Your might have heard that Unreal's smart pointers (TSharedPtr
, TSharedRef
, TWeakPtr
, TUniquePtr
) should not be used on UObjects
, because the UObject
memory management system is geared for game code, which is much more garbage collection oriented.
A clear distinction between what is exposed to the uobject memory management system and what not should not be foreign. That's the reason this article exists.
Drawing the line
Not compatible with UObject | Compatible with UObject |
TSharedPtr , TSharedRef , TWeakPtr , TUniquePtr |
|
TWeakObjectPtr
doesn't prevent Garbage Collection and always returns NULL
when the underlying object is destroyed (regardless of being marked with UPROPERY()
, in case you wonder).
A great example of where TWeakObjectPtr
shines is its usage by the series of DECLARE_DYNAMIC_
delegate variants. (It actually uses the non-generic FWeakObjectPtr
, which implies UObject
only.)
The macros that declare a dynamic delegate create a class that inherits from TBaseDynamicDelegate
, which inherits from TScriptDelegate
. TScriptDelegate
stores the object bound to the delegate as FWeakObjectPtr
, which makes a lot of sense if you think about how you would logically expect the ownership to be handled:
The binding should not prevent the object from being destroyed!
Ways the engine uses TWeakPtr or TSharedPtr
The primary way UGameplayTagManager
stores and retrieves Gameplay Tags is by storing them as nodes in a tree-like fashion, parallel to how Gameplay Tag strings maintain their hierarchy with dot delimiters.
Those nodes are stored as TSharedPtr<FGameplayTagNode>
, which reminds me of how std::shared_ptr
is commonly used for creating tree-like relationships.
After all, lots of engine modules make use of TSharedPtr
and TWeakPtr
for managing non-uobject classes, but those classes cannot participate in unreal separate memory tracking system geared for game code.
Let's not forget mentioning TSharedFromThis
, which is the class many classes derive from to directly get an instance as TSharedRef
.
It's used extensively by many things which are rarely related to the entire surface-level Player Controller, Player State, Pawn, etc. paradigm.Â
Key takeaways
TSharedPtr
is excellent for managing the lifetime of tree-like nodes. These nodes can't simultaneously participate in Unreal's separate memory tracking system geared for game-code, thus should not beUObject
.Garbage collection reliance allows an easy scripting experience, as the allocated memory gets handled automatically and intelligently.
Raw pointers that aren't marked with
UPROPERTY()
are not exposed to the reflection system, and thus won't explicitly be set tonullptr
when the address should no longer be accessed.IsValid()
could crash the game; it might be a dangling pointer because it wasn't explicitly set tonullptr
.Suppose you're storing the address of garbage collectable class instances through a pointer. In that case, it's great to mark that field with
UPROPERTY()
, so the reflection system can null it for you, makingnullptr
a reliable way to test the validity of an object.IsValid()
is a more streamlined approach for game code, as it combines some flag tests such asIsPendingKill
(orIsGarbage
since UE5).