UPROPERTY(hard references) vs TWeakObjectPtr and Garbage Collection

I could’nt find much details about this but only this:

  1. For example lets say that in ActorComponent “X” i use EITHER:

    • A weak pointer

       TWeakObjectPtr<AActor*> TheActor; 
      
    • OR i use UPROPERTY()

       UPROPERTY()
       AActor* TheActor;  
      
  2. Ofc i initialise it to an actor in the scene in BeginPlay for example

  3. Lets say that in ActorComponent “Y” i destroy TheActor.

My understanding of what will happen in “X”:

  1. If A weak pointer was used

    • TheActor.IsValid() will return false since the object has been destroyed
  2. If UPROPERTY was used

    • TheActor will be set to null automatically after “Y” has destroyed it

    • if(TheActor) will evaluate to false,

Unreal docs says:

Note this does not mean that all UObject* variables must be UProperties . If you want an Object pointer that is not a UProperty , consider using TWeakObjectPtr . This is a “weak” pointer, meaning it will not prevent garbage collection, but it can be queried for validity before being accessed and will be set to null if the Object it points to is destroyed.

My Question:
I may have a confusion on Garbage Collection, could you correct me please, but if a reference using UPROPERTY is automatically nulled doesn’t that mean it has been Garbage Collected?

  • However I found in some resources that UPROPERTY() can prevent Garbage collection, are they right? Example
  • If they seem to be doing the same thing I don’t underestand why we don’t just use UPROPERTY on all UOBJECT references?

Actors are always explicitly destroyed. You can not keep an actor alive by holding a pointer to it (the world marks it as pending kill which flags to the GC to clear it up). Other UObjects on the other hand, need a UPROPERTY to keep it alive, as the GC will check to see if any UPROPERTY is holding a pointer to that object, if not, it cleans it up.

So a TWeakObjectPtr for an AActor is not really needed. For normal UObjects, it makes sense, if the class that is holding a pointer to it should not keep it alive.

There IS come caveats with ActorComponents though. The DestroyActor function will actually mark all components the Actor has as PendingKill. BUT components can be GC’d if they are not held by a strong pointer that is UPROPERTY as AActor does NOT hold components in a UPROPERTY container.

So in a nutshell, TWeakObjectPtr makes sense if you don’t want to keep an Object alive but want to hold a pointer to it. This mainly is related to UObject, and not so much with Actor/ActorComponent (with some caveats explained below). BUT in order for an object to stay alive it must be in the root set OR have a raw pointer with UPROPERTY. Actors are handled by the world, and the world keeps them alive.

One of the other uses for a TWeakObjectPtr over a UPROPERTY is you don’t need to be in a full reflected scenario. Say for instance you have a normal non reflected struct. You can hold a TWeakObjectPtr to an Actor, but you can not hold a UPROPERTY to that Actor. One of the caveats with the GC system is it needs a full line of reflection.

TWeakObjectPtr<AActor> WeakActor;

This does not need to be UPROPERTY for the GC to invalidate the pointer. Another example is this:

USTRUCT()
struct FMyStruct
{
    GENERATED_BODY()

    TWeakObjectPtr<AActor> WeakActor; //This will be marked stale/invalid when actor is gone.

    UPROPERTY()
    AActor* NonWeakActor; //This will not be nulled when the actor is destroyed, and will point to invalid memory (dangling pointer)
};



UCLASS()
class MYGAME_API UMyObject : public UObject
{
    GENERATED_BODY()

    FMyStruct MyStruct;
};

In the above you can not use UPROPERTY on your pointer, so you need to hold TWeakObjectPtr here inside your struct. Even though your struct is a USTRUCT you broke the reflection chain by not using UPROPERTY on the FMyStruct. You could hold a raw pointer here with UPROPERTY() but this will NOT be nulled out and will be dangling if the Actor goes away.

I can give more examples, but hopefully this helps?

8 Likes

Oh wow, Thank you :smiley: I see, that is some well explained information to absorb.

From the docs I understood that USTRUCTS aren’t garbage collected:

UStructs , as mentioned earlier, are meant to be a lightweight version of a UObject . As such, UStructs cannot be garbage collected. If you must use dynamic instances of UStructs , you may want to use smart pointers instead, which we will go over later.

Is that why the UPROPERTY NonWeakActor is not nulled?

No not at all. What you read is that USTRUCTS are not cleaned up by GC. So if you have a struct on the heap, the GC wont go an destroy it (if you created it via “new”), and instead should use a TSharedPtr or TUniquePtr.

1 Like