Using TWeakPtr and TSharedPtr

How does Unreal's smart pointer library work with UObjects? Let's establish a clear distinction.

UObject

Your might have heard the story about how using Unreal's smart pointers (TSharedPtr, TSharedRef, TWeakPtr, TUniquePtr) causes unexpected results 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 must not be foreign. That's the reason this article exists.

Drawing the line

Not compatible with UObject TSharedPtr, TSharedRef, TWeakPtr, TUniquePtr
Compatible with UObject

TWeakObjectPtr

TWeakObjectPtr doesn't prevent GC and becomes NULL when the underlying object is destroyed. It's a great way to add detail on how you're using it. Most likely as an observer, while not 'telling the engine it should not be GC'ed because your UPROPERTY() marked field is storing the address.'

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.

More 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 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 back-end modules make use of TSharedPtr and TWeakPtr for doing all kinds of non-gameplay related things. You'd also see TWeakPtr being used on FGCObjects once in a while.

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. Again, not really related to tangible gameplay mechanics, or a part of the entire surface-level Player Controller, Player State, Pawn, etc. paradigm.

Key takeaways

  1. 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 would preferably not be UObjects or classes derived from FGCObject.
  2. Using TWeakPtr for classes derived from FGCObject works well, while using TSharedPtr creates tricky situations.
  3. Garbage collection reliance is the tradeoff made for attaining an easy scripting experience, as the allocated memory gets handled automatically and intelligently.
  4. Pointers that aren't marked with UPROPERTY() are not exposed to the reflection system, thus won't explicitly be set to nullptr when the address is no longer valid. IsValid() could crash the game; it might be a dangling pointer because it wasn't explicitly set to nullptr.
  5. If you're storing the address of garbage collectable class instances through a pointer, it's great to mark that field with UPROPERTY(), so the reflection system can null it for you, making comparisons to nullptr a reliable way to test the existance in memory.

    (nullptr checks are somewhat relatively speaking low-level. Use global bool IsValid(const UObject* Test) if you want to combine a slightly higher level IsPendingKill flag check, which is a more streamlined approach for game code.)