[Unreal C++]意図しないガベージコレクションを起こす
ガベージコレクション(以下GCと略す)とは、使われなくなったオブジェクトを自動で検知・破棄しメモリの無駄を減らす機構のことです。
オブジェクトが破棄されていない間はIsValidの結果がTrueになり、破棄された後はFalseになることが期待されます。
このように設定すると、オブジェクトはGCの対象からはずれます。RF_Standaloneは参照が消えてもデータ編集用に保持するフラグ、RF_MaskAsRootSetは参照グラフ上のルートセットとするもので、これを設定するとGCの対象外になります。
UE4にもこれが実装されていますが、どのように動作しているかはいまいちわかりません。
今回は、GCによってオブジェクトが破棄される様を確認することで、リバースエンジニアリング的にその挙動を見ていきたいと思います。
今回は、GCによってオブジェクトが破棄される様を確認することで、リバースエンジニアリング的にその挙動を見ていきたいと思います。
UE4におけるガベージコレクション
アンリアルのオブジェクトのハンドリングに書いてありました。
オブジェクトをGCの対象から外す方法は、UPROPERTY指定子を付けた変数によって保持すること、だそうです。
Blueprint上の変数は全てUPROPERTY指定子がついたものといえます。よって、Blueprint OnlyのプロジェクトでGCを意識することはありません。更に、ActorやComponent等のクラスは明らかにレベルやActorによって参照が保持されているので、意図しないGCの対象になることはないでしょう。意図しないGCの対象にするためのポイントをまとめます。
- 動的に生成する(コード上でNewObjectを使う)
- UObjectクラスを継承したクラス(ActorやComponentではない)
- UPROPERTY指定子をつけた変数で保持しない
オブジェクトを生成する
まず、プロジェクトを立ち上げて、新規C++クラスを作成します。
![]() |
図1 UObject継承クラスを作る |
「全てのクラスを表示」して「Object」を選択して次へをクリックします。
![]() |
図2 クラス名をつける |
クラス名はGameVarとしておき、何も変更しません。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "UObject/NoExportTypes.h" | |
#include "GameVar.generated.h" | |
UCLASS() | |
class BLOGTEST_API UGameVar : public UObject | |
{ | |
GENERATED_BODY() | |
}; |
GCによって破棄されたオブジェクトにアクセスする
UPROPERTY指定子をつけた変数によって保持しないことで、生成したオブジェクトがGCによって破棄されることを確認してみます。今回はGameModeに実装してみました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "GameFramework/GameModeBase.h" | |
#include "BlogTestGameModeBase.generated.h" | |
UCLASS() | |
class BLOGTEST_API ABlogTestGameModeBase : public AGameModeBase | |
{ | |
GENERATED_BODY() | |
public: | |
TArray<class UGameVar*> gvs | |
UFUNCTION(BlueprintCallable, Category = GCTest) | |
void CreateAndAddGameVar(); | |
UFUNCTION(BlueprintCallable, Category = GCTest) | |
TArray<class UGameVar> GetGameVariables() const; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "BlogTest.h" | |
#include "BlogTestGameModeBase.h" | |
#include "GameVar.h" | |
void ABlogTestGameModeBase::CreateAndAddGameVar() | |
{ | |
gvs.Add(NewObject<UGameVar>()); | |
} | |
TArray<UGameVar*> ABlogTestGameModeBase::GetGameVariables() const | |
{ | |
return gvs; | |
} |
変数gvsは、GCの対象外にするためにUPROPERTY指定子を付けていません。CreateAndAddGameVar関数は、UGameVarオブジェクトを新たに生成し、gvsに格納する関数です。これらをレベルブループリント上で次のようにします。
![]() |
図3 GCによる破棄を確認するためのBlueprintの実装 |
このレベルのWorldSettingsでGameModeをBlogTestGameModeBaseに変更して、実行してみると……
![]() |
図4 オブジェクトのIsValid出力結果(約1分後) |
約1分後{1}に、出力がTrueからFalseに切り替わっており、Objectが破棄されたことが確認できました。
GCの回避
BlogTestGameModeBase.hの変数gvsにUPROPERTY指定子を挿入すれば、意図しないGCを回避できます。
しかし、スマートに書けない場合もあります。
例えば、UE4.14以前はTMapにUPROPERTYが付けられなかったため、GC回避用にUPROPERTYをつけたTArrayを別途用意したりする必要がありました。このような実装をするとTMapから要素を削除する際、関連するTArrayから値を削除するなどの手間が増えてしまい、コードが複雑で非効率になってしまいます。
UE4.14以降、TMapのUPROPERTY化が進み、そのようなことをする必要は無くなりましたが、似たようなことが起きるかもしれません。そこで、別の方法でGCから回避する方法を紹介します。NewObjectのObjectFlagsを次のように設定します。
例えば、UE4.14以前はTMapにUPROPERTYが付けられなかったため、GC回避用にUPROPERTYをつけたTArrayを別途用意したりする必要がありました。このような実装をするとTMapから要素を削除する際、関連するTArrayから値を削除するなどの手間が増えてしまい、コードが複雑で非効率になってしまいます。
UE4.14以降、TMapのUPROPERTY化が進み、そのようなことをする必要は無くなりましたが、似たようなことが起きるかもしれません。そこで、別の方法でGCから回避する方法を紹介します。NewObjectのObjectFlagsを次のように設定します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
UGameVar* gameVar = NewObject(GetTransientPackage(), NAME_None, RF_Standalone | RF_MarkAsRootSet); |
Blueprint上で生成する
「クラスからオブジェクトを構成(ConstructObjectFromClass)」ノードというものがあります。
![]() |
図5 クラスからオブジェクトを構成 |
これもNewObjectのようにオブジェクトを動的に生成しますが、こちらは変数で保持しなくてもGCの対象にはなりません。理由ですが、おそらくノードのReturn Valueに値がキャッシュされており、それが参照を保持しているための考えられます。よっぽど変なことをしなければ、意図しないGCの対象になることはないでしょう。
おわりに
UE4.14より前のバージョンでは、TMapを使ってアレコレするとGCの問題に引っかかることがありましたが、現在はそういうこともなくなりました。
今後こういう問題は益々減ると思いますが、今回の検証をアンチパターンとして心に留めておけば、同じようなことになったときに解決の糸口になるかもしれません。
今後こういう問題は益々減ると思いますが、今回の検証をアンチパターンとして心に留めておけば、同じようなことになったときに解決の糸口になるかもしれません。
{1} [プロジェクト設定]->[Garbage Collection]->[Time Between Purging Pending Kill Objects]の時間後にGCが発行されます。デフォルトは1分です。
この記事は次のバージョンで作成されました。
Unreal Editor(4.15.1-3348071+++UE4+Release-4.15)
Microsoft Visual Studio Community 2015(14.0.25425.01 Update 3)
Vtinccegaeya Rosa Harris https://wakelet.com/wake/1UcavJXy6TE63J0sdb7Fc
返信削除exircopalm