UEのガベージコレクションと UPROPERTY・TObjectPtr・TWeakObjectPtr¶
一言で言うと¶
UEはUObjectに限定したマーク&スイープGCを自作しています。「C++なのにGC」——第7部で学んだ2つの世界がUEでは同居しており、「このポインタはGCに見えているか?」が生死を分ける独自ルールになります。
仕組み(概要)¶
- ルート(GameInstance、レベル等)から UPROPERTYで宣言されたポインタを辿り、到達できないUObjectを回収(マーク&スイープ)。既定では一定間隔+レベル遷移などで実行
- C#のGCとの決定的な違い: .NETは実行中のあらゆる参照(スタック・レジスタ含む)を追跡できるが、UEのGCはリフレクションに登録された(=UPROPERTYの)参照しか見えない
UCLASS()
class ATurret : public AActor {
GENERATED_BODY()
UPROPERTY() // GCに「この参照を辿って」と教える
TObjectPtr<AEnemy> Target; // → Targetが生きている限りEnemyは回収されない
// (=参照が対象を延命する。C#と同じ感覚)
AEnemy* RawTarget; // UPROPERTYなし: GCから不可視!
// (1) Enemyがこれ以外から参照されなければ回収される
// (2) 回収後もRawTargetは残る=dangling(C++の事故がそのまま)
};
UPROPERTYを付け忘れた生ポインタは「延命もしない・無効化もされない」最悪の存在——UE C++の事故原因第1位です。
ポインタ型の使い分け(UE5)¶
| 型 | 意味 | 対応する標準C++の概念 |
|---|---|---|
UPROPERTY() TObjectPtr<T> |
所有的・強参照(延命する)。UE5でのメンバ宣言の標準形 | shared_ptr的(ただし台帳方式) |
TWeakObjectPtr<T> |
弱参照(延命しない+生死を問える IsValid()) |
weak_ptr |
TSoftObjectPtr<T> |
アセットへのパス参照(ロードを強制しない) | Proxy(遅延ロード) |
生ポインタ T* |
関数引数・ローカルなど一時的な借用のみ | 非所有生ポインタ(借用) |
- 使い分けの原則は第7部と同型: メンバとして保存するならGCに見える形(TObjectPtr+UPROPERTY)か弱参照(TWeakObjectPtr)、関数スコープの借用は生ポインタで可
- 「相手(敵・アイテム)が先に消えるかもしれない参照」は
TWeakObjectPtr+IsValid()——ロックオンカメラ問題のUE公式解
Actorの破棄はGCだけではない¶
DestroyActor() は「ゲーム的な破棄」(レベルから除去・マーク)で、メモリ回収は後のGCが行う2段階です。破棄済みだが未回収の期間があるため、生存確認は IsValid(actor)(nullチェックでは不十分——UnityのDestroyと== nullのカスタム比較に似た構図)。
標準C++ではどう書くか / なぜUEはGCか¶
標準C++なら所有の木+unique_ptr+ハンドルで設計します。UEがUObjectにGCを選んだ理由:
- レベル・Actor・コンポーネント・アセット・Blueprintが相互参照する巨大グラフであり、所有の木を全プロジェクトに強制するのは非現実的
- Blueprint(非プログラマ)が参照を作る——所有権設計を要求できない
- エディタでのオブジェクト差し替え・ホットリロードに、到達可能性ベースの回収が都合よい
一方、非UObject(FVector、TArrayの中身、自作の純C++クラス)は完全に標準C++の世界(RAII・値セマンティクス)。UEプログラマは2つのメモリモデルを常に区別して書いているのが実態です。
Unity開発者が誤解しやすい点¶
- 「C#と同じで参照してれば生きてる」— UPROPERTYの参照だけが延命する。ローカルのC++変数に持っただけでは(次のGCで)消えうる
- 「Destroyしたらnullになる」— Unityの
==null偽装に慣れていると、UEの「IsValidで確認」を忘れる。生ポインタは何も起きない(dangling) - UObjectに
std::shared_ptrを使う——禁止(2つの管理系が衝突)。UObjectはUE式、非UObjectは標準式、と世界を分ける
理解度チェック¶
- UEのGCがC#のGCと違い「UPROPERTYしか見えない」ことの帰結を2つ(延命されない/danglingになる)説明できますか。
- TObjectPtr / TWeakObjectPtr / TSoftObjectPtr / 生ポインタの使い分けは?
- UObjectと非UObjectでメモリ管理の流儀がどう分かれますか。
演習¶
「ロックオンカメラ(ターゲットの敵が消えるかもしれない)」を、(a) 標準C++(第7部演習の解答)、(b) UE(TWeakObjectPtr+IsValid)の両方の骨格で書き、同じ問題への2つの解法として対比してください。
前: UObject・Actor・ActorComponent | カテゴリ目次 | 次: リフレクションとUHT