Please help me understand rootset and garbage collection

I understand after reading this documentation that class pointers which are not marked with UPROPERTY or which are not in a container will be garbage collected on next phase of garbage collector.

however if the pointer is attached to rootset (some root component) for example with SetupAttachment() function, will that object be garbage collected if it’s not marked with UPROPERTY?

Following documentation tutorial demonstrates this scenario LINK
Now what confuses me here is that documentation on first link does not confirm this so I don’t know for sure. it looks like tutorial from second link is introducing a dangling pointer because it’s not marked with UPROPERTY

snippet code:

header file



 protected:     UPROPERTY(EditAnywhere)  
                      USpringArmComponent* OurCameraSpringArm;

                      // will this become dangling pointer?
                      UCameraComponent* OurCamera; 

cpp file



 //Create our components
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
urCameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));

OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("GameCamera"));
OurCamera->SetupAttachment(OurCameraSpringArm, USpringArmComponent::SocketName); 

Here UCameraComponent* OurCamera; looks like it’s going to be garbage collected because it’s not marked with UPROPERTY nor it’s inside a container, therefore it will become dangling pointer, at least that’s what documenation says, is that correct?

AFAIK You are correct - SetupAttachment does not convey reference ownership.

"Any UObject pointer stored in a UPROPERTY or in a UE4 container class (such as TArray) is considered a “reference” for the purposes of garbage collection. "

Make it a UPROPERTY to be safe.

Thank you for input, in the mean time I’ve also read the wiki article about memory management LINK

Here is relevant infomation from the wiki:



 YourObjectInstance->AddToRoot(); 

Now from what I understand AddToRoot() is the same as SetupAttachment() regarding reference counting but I’m not sure 100%

It would be great to know for sure if adding to rootset with either of 2 above functions is enough, ie. no need for UPROPERTY or container storage.
Otherwise it looks like epic team documentation writers teach people how to write chrash code

Can somebody please confirm that AddToRoot() and SetupAttachment() is the same as declaring variable with UPROPERTY and to prevent garbage collection? it’s a very basic question.

It’s not the same thing. You can read this to more info:
https://wiki.unrealengine.com/UPROPERTY

I really appreciate your answers however the documentation is misleading, it says one thing and then on another page it says the opposite.

Here is simple code that goes against the documentation you quoted.
You can copy and paste code to test it, make a new project, create new actor class “MyActor” and copy paste code bellow

You will see that Garbage collector does not do it’s job and will not free memory for pointers not marked with UPROPERTY
Only object made with NewObject<> template will be garbage collected because it’s not it the root!

The question now is why doesn’t garbage collector delete objects as told in documentation?
Make sure you setup startup map with actor in it in project settings before running debugger and also set garbage collector to run every 5 seconds

what’s even more interesting is that one of BAD and BAD2 pointers in code bellow won’t be freed even without attachement call, CreateDefaultSubobject() function

MyActor.h


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class TEST_API AMyActor : public AActor
{
    GENERATED_BODY()

public:
    // Sets default values for this actor's properties
    AMyActor();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    class USpringArmComponent* BAD;
    class USpringArmComponent* BAD2;
    class USpringArmComponent* BAD3;
};

MyActor.cpp


#include "MyActor.h"
#include "GameFramework/SpringArmComponent.h"


// Sets default values
AMyActor::AMyActor()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    BAD = CreateDefaultSubobject<USpringArmComponent>("LET'S SEE WHAT YOU ARE MADE OF!");
    BAD2 = CreateDefaultSubobject<USpringArmComponent>("KABUM!");

    BAD3 = NewObject<USpringArmComponent>();
}

// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
    Super::BeginPlay();

    if (RootComponent->GetFName() == BAD->GetFName())
    {
        UE_LOG(LogTemp, Warning, TEXT("BAD is root component"));
    }

    if (RootComponent->GetFName() == BAD2->GetFName())
    {
        UE_LOG(LogTemp, Warning, TEXT("BAD2 is root component"));
    }


}

// Called every frame
void AMyActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    BAD2->IsPendingKill();

    // this is how to learn UE4
    if (!BAD->IsValidLowLevel() || !BAD2->IsValidLowLevel())
    {
        UE_LOG(LogTemp, Error, TEXT("BAD or BAD2: Invalid object!"));
    }

    if (!BAD3->IsValidLowLevel())
    {
        UE_LOG(LogTemp, Error, TEXT("BAD3: Invalid object!"));
    }

    if (BAD->IsPendingKill())
    {
        UE_LOG(LogTemp, Warning, TEXT("BAD: Pending kill but memory valid!"));
    }

    if (BAD2->IsPendingKill())
    {
        UE_LOG(LogTemp, Warning, TEXT("BAD2: Pending kill but memory valid!"));
    }

    if (BAD3->IsPendingKill())
    {
        UE_LOG(LogTemp, Warning, TEXT("BAD3: Pending kill but memory valid!"));
    }

}



As we can read here

BAD and BAD2 are created with CreateDefaultSubobject, which (I think) auto-register component to given Actor. So, component are reachable (ULevel->AActor->UActorComponent).
In NewObject<> version you not provide Outer, so this means that this component is unreachable and garbage collected.

PS. Personally I am not a big fan o GC. because it blurs ownership.

Default sub objects are assumed to always exist while outer exists, they are never forced to delete by GC while outer is valid.

CreateDefaultSubobject adds the component into ConstructedSubobjects which is a TArray. This is why they are not garbage collected, they are in the RootSet, and when GC crawls it finds them.

Specifically
/** List of all subobject names constructed for this object */
mutable TArray<FName, TInlineAllocator<8>> ConstructedSubobjects;

"Any UObject pointer stored in a UPROPERTY or in a UE4 container class (such as TArray) is considered a “reference” for the purposes of garbage collection. "

Thank you guys for replies! very appreciated.

It looks I misread the tiny quote which @Eamer posted here, thank you for this!
[USER=“434”]BrUnO XaVIeR[/USER] @OptimisticMonkey, yes that is what I suspected, but other documentation pages do not reveal you that, that GC takes rootset as exception to UPROPERTY and container storage all the time but couldn’t find documentation to confirm. thanks a lot!

Actually i found an interesting article that explain well how Garbage collection works within unreal

Hope this will help

1 Like