Garbage collection and UPROPERTY

Mock Q3 (Partial) + the "bugs that pass testing" list from Q2 · 8 min

In one breath

Unreal's GC keeps an object alive only if it can reach it through references it knows about: UPROPERTY members or AddReferencedObjects. A raw pointer without UPROPERTY is invisible. The object gets collected on the next GC pass, the pointer dangles, and you crash minutes later somewhere unrelated. UPROPERTY is the fix, not TObjectPtr.

The question

A member UBodySetup* ClusterBodySetup; with no UPROPERTY, created with NewObject<UBodySetup>(this), works in testing but crashes in physics a few minutes into a session. What's happening? Give two fixes and when to choose each. Is TObjectPtr alone a fix?

What's happening

Fixes, and when to use each

FixUse when
UPROPERTY() TObjectPtr<T> X;Default. The owner is a UObject. You also get serialization, editor visibility and reference replacement.
FGCObject + AddReferencedObjectsThe owner is not a UObject (Slate widget, toolkit, plain C++ class) but must keep a UObject alive.
TStrongObjectPtr<T>Same as above, for a single pointer. It's a convenience wrapper.
AddToRoot() / RemoveFromRoot()Rarely. It's global lifetime until un-rooted, so forgetting leaks it. For process-lifetime singletons.
TWeakObjectPtr<T>Not a keep-alive. It lets you safely detect the object is gone (Get() → null). For things you don't own.

TObjectPtr vs raw pointer

In WadOfGore

UPROPERTY() AWarriorBaseCharacter* OwningCharacter; in the anim instance. The UPROPERTY() with no specifiers exists purely so the GC tracks it. You know why it's there now.

Bugs that pass a quick test (know three by heart)

40-second answer

"The GC only follows reflected references, so a raw pointer without UPROPERTY doesn't keep it alive, and being its Outer doesn't either. The first GC pass collects it, the pointer dangles, physics crashes on next use. Fix: UPROPERTY on a TObjectPtr if the owner is a UObject; FGCObject or TStrongObjectPtr if it isn't. AddToRoot only for global lifetime. TObjectPtr alone doesn't fix it. UPROPERTY is what the GC sees."

Quiz yourself

How does Unreal's GC decide what to destroy?

Mark-and-sweep: from the root set, follow every reflected reference (UPROPERTY / AddReferencedObjects), mark what's reached, destroy the rest.

Does being an object's Outer keep it alive?

No. Outer controls naming and package, not reachability.

Why does the crash happen minutes later, somewhere else?

GC runs periodically, so the object survives short tests. After it's collected, the raw pointer still holds the old address, and the crash is wherever it's next used.

Is TObjectPtr<T> without UPROPERTY safe?

No. UPROPERTY is what makes the reference visible to the GC.

A Slate widget (not a UObject) needs to keep a material alive. What do you use?

FGCObject with AddReferencedObjects, or a TStrongObjectPtr.

Does TWeakObjectPtr keep things alive? What's it for?

No. It's for referring to something you don't own: it returns null once the object is gone, instead of dangling.

Name three bugs that pass a quick test in Unreal.

Any three: missing UPROPERTY (GC), missing Modify/transaction (undo), transient state (save/reload), live coding vs restart, editor-only code in a packaged build.