UPROPERTY を使わずにオブジェクトをガーベジ コレクションの対象から外す方法

別の調べ物をしていたら偶然見つけたので、書いてみました。

#UPROPERTY を付ければガーベジコレクションの対象から外れる

UE4 では、オブジェクトのインスタンスは UPROPERTY が付いていない変数に保持しても、ガーベッジコレクションの対象となってしまい、ガーベジコレクションが実行されると破棄されます。

なので、**「オブジェクトを保持する変数は UPROPERTY を付ける」**が UE4 の基本的なルールの一つとなっています。

ただし、UPROPERTY を付けるためにはクラス、もしくは構造体に UCLASS、もしくは USTRUCT が付いていることが前提となります。

# 実は UPROPERTY を使わなくても外す方法はある
では、「ガーベジコレクションの対象から外すためにはこれらのクラス、もしくは構造体にしか変数を持てないのか？」というと、実は持てます。

UE4 には、**FGCObject** というクラスが用意されていて、このクラスには以下のような純粋仮想関数が用意されています。

```cpp:
virtual void AddReferencedObjects( FReferenceCollector& Collector ) = 0;
```

この関数をオーバーライドして、引数として渡される Collector に対象から外したいオブジェクトを追加すると、その追加したオブジェクトはガーベジコレクションの対象から外れます。

確認してみましょう。

```cpp:AssetLoadActor.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/Actor.h"
#include "AssetLoadActor.generated.h"

UCLASS()
class MYPROJECT_API AAssetLoadActor : public AActor
{
	GENERATED_BODY()
	
public:
	// ロードするアセット
	UPROPERTY(EditAnywhere)
	FStringAssetReference AssetID;

	// GC から外すか？
	UPROPERTY(EditAnywhere)
	bool bExcludeGC;

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

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	
	// Called every frame
	virtual void Tick( float DeltaSeconds ) override;

	// GC を操作する構造体(アクターで継承するとエラーになるので、別途持たせる。)
	struct FGCController : public FGCObject
	{
		AAssetLoadActor* Actor;
		UObject* Target;

		FGCController()
			: FGCObject()
			, Actor()
			, Target()
		{
		}

		virtual void AddReferencedObjects(FReferenceCollector& Collector) override
		{
			// フラグが立ってたら、GC から外す
			if (IsValid(Actor) && Actor->bExcludeGC)
			{
				Collector.AddReferencedObject(Target);
			}
			else
			{
				// GC に含めるので、初期化する
				Target = nullptr;
			}
		}

	} GCController;
		
};
```

```cpp:AssetLoadActor.cpp
// Fill out your copyright notice in the Description page of Project Settings.

#include "MyProject.h"
#include "AssetLoadActor.h"

#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/GameplayStatics.h"


// Sets default values
AAssetLoadActor::AAssetLoadActor()
{
 	// 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;
}

// Called when the game starts or when spawned
void AAssetLoadActor::BeginPlay()
{
	GCController.Actor = this;

	Super::BeginPlay();
	
}

// Called every frame
void AAssetLoadActor::Tick( float DeltaTime )
{
	APlayerController* Controller = UGameplayStatics::GetPlayerController(GetWorld(), 0);
	if (IsValid(Controller))
	{
		// Enter が押されたらアセットを同期ロードし、それを FGCController に監視させる
		if (Controller->WasInputKeyJustPressed(EKeys::Enter))
		{
			GCController.Target = AssetID.TryLoad();
		}
	}

	// アセットの有無を表示
	if (IsValid(GCController.Target))
	{
		UKismetSystemLibrary::PrintString(GetWorld(), TEXT("true"));
	}
	else
	{
		UKismetSystemLibrary::PrintString(GetWorld(), TEXT("false"));
	}

	Super::Tick( DeltaTime );

}
```

これは Enter キーを押したら AssetID で指定されているアセットをロードするだけのアクターです。ロードしたアセットが破棄されているかは **true or false** で毎フレーム画面上に表示されます。

このアクターの挙動をスタンドアローン実行[^1]して見てみると、**bExcludeGC** の設定によって表示されるテキストが違うことがわかります。

双方とも、**アセットをロードする前は常に false** が表示されますが、アセットをロードした後の挙動が異なります。

**bExcludeGC** が **true** の場合、**アセットをロードした後は常に true** が表示されます。
反対に、**bExcludeGC** が **false** の場合、**アセットをロードした後は true** が表示されますが、**ガーベジコレクションが実行された後は false** が表示されます。

このように、この方法でガーベジコレクションの対象から外せることは確認できました。
ただ、UPROPERTY を使う場合と比べて手間がかかりますので、特に理由がなければこの方法は使わなくてもいいのではないかと思います。

……まぁ、自分は理由があって結果的に使うことになりましたけどねw

[^1]: エディタ実行ではロードしたアセットが破棄されないので、正しい挙動が確認できない。