---
title: UObjectの動作原理
tags:  #ゲーム開発 #ue4  
author: [株式会社ヘキサドライブ](https://www.docswell.com/user/hexadrive)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/Y765PM1QEV.jpg?width=480
description: これは、社内勉強会で使われたスライドです。  UE4においてC++からUObjectを扱う際に必要な基礎知識をまとめました。  UObjectの扱いは従来のC++のメモリ管理とは異なる考え方が必要であるにも関わらず、学習の取っ掛かりとなる資料が見つけにくいという現状があります。  本資料は、UObjectの仕組みとよくある失敗例への対処法を学び、初学者がスムーズにプロジェクトに合流できるように手助けすることを目標としています。
published: June 05, 20
canonical: https://www.docswell.com/s/hexadrive/5389PL-2020-06-05-182602
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/Y765PM1QEV.jpg)

UObjectの動作原理
株式会社ヘキサドライブ
堀井 陽介


# Page. 2

![Page Image](https://bcdn.docswell.com/page/G75YKZ4274.jpg)

目次
UObjectとは？
UObjectの意義
GCとライフサイクル
バグ事例集
全体のまとめ
おまけ


# Page. 3

![Page Image](https://bcdn.docswell.com/page/9J2GWRV1JR.jpg)

UObjectとは？


# Page. 4

![Page Image](https://bcdn.docswell.com/page/DEYVLD34EM.jpg)

UObjectとは？
https://docs.unrealengine.com/ja/Programming/UnrealArchitecture/Objects/index.html
◆ ゲーム オブジェクトを処理する堅牢なシステム
◆ 機能の一例
ガーベジ コレクション、参照の更新、リフレクション、シリアライズ、デフォルトのプロパティ変
更の自動更新、プロパティの自動初期化、エディタの自動統合、ランタイムに利用可能な型情
報、ネットワーク レプリケーション...


# Page. 5

![Page Image](https://bcdn.docswell.com/page/VJNM46V678.jpg)

UObjectとは？
◆ 以下すべてUObject
Uから始まるUTexture2D, UActorComponent ...
Aから始まるAActor, ACharacter, AGameMode...
コンテンツブラウザの全て
ワールドアウトライナの全て
Unreal C++で書くなら、
大体のクラスはUObject継承
独特のメモリ管理機構とGC
new/deleteの置き換えとも言える


# Page. 6

![Page Image](https://bcdn.docswell.com/page/YE9NQL6RE3.jpg)

UObjectの意義
基本の使い方と意義


# Page. 7

![Page Image](https://bcdn.docswell.com/page/GE8KGXZG7D.jpg)

UObjectの意義
◆ 少ないコードで安全にゲームリソースを管理
参照の有無に関わらず、任意タイミングで破棄できるので、コンテンツ都合で寿命をコントロー
ルしたいゲーム制作において便利
解放忘れ、循環参照、ダングリングポインタ等のありがちなコーディング上のミスが起こりにく
いよう設計されている


# Page. 8

![Page Image](https://bcdn.docswell.com/page/LELZG8DXJR.jpg)

UObject以前について
◆ UObjectの有り難みを語るために、既存のシステムによるリソース管理を
おさらい
● new/delete
● AddRef/Release
◆ ポイントは３つ
✓ 共有しやすさ
✓ バグの入りやすさ
✓ 寿命管理


# Page. 9

![Page Image](https://bcdn.docswell.com/page/4JMVQ6P3JW.jpg)

new/delete
◆ new/deleteをゲームリソースの管理として考えた場合
● 共有しにくい
● メモリ由来のバグを入れやすい
● SAFE_DELETE、などのマクロで頑張った


# Page. 10

![Page Image](https://bcdn.docswell.com/page/PJR28P1RE9.jpg)

AddRef/Release
◆ COMオブジェクト、又は参照カウンタ型オブジェクトをゲームリソースの管
理につかう場合
● 共有はしやすくなった
スレッド間でも共有しやすい
● 依然、バグを作りやすかった
AddRef忘れ、Release忘れ、循環参照
→どこが原因かぱっと見わからない
● もういらないことが明らかなのに他の場所で参照していると解放されない


# Page. 11

![Page Image](https://bcdn.docswell.com/page/PEXD83YY7X.jpg)

UObjectを使ったリソース管理
◆ UObjectを使ったリソース管理のメリット
● 共有が非常に楽
● マークアンドスイープのGC（ガーベジコレクション）であり、 循環参照OK
● 他から参照されていても任意のタイミングで安全に破棄できる
● リソース管理の労力を極限まで省き、コンテンツの実装に集中できる


# Page. 12

![Page Image](https://bcdn.docswell.com/page/3EKXKY347D.jpg)

UObjectを使ったリソース管理
◆ UObjectの難しさ
● ネット上に情報が少ない
● UPROPERTYという、C++に存在しない構文の学習コスト
● GCの負荷はかかってくる
● PendingKillという、C++的には有効なポインタだが、存在しないモノとして扱われる中間
状態を意識する必要


# Page. 13

![Page Image](https://bcdn.docswell.com/page/L735Z9GDE5.jpg)

GCとライフサイクル


# Page. 14

![Page Image](https://bcdn.docswell.com/page/87DDRG127G.jpg)

C++でのUObjectの保持のしかた
◆ ポインタの上にUPROPERTYを付ける事でGCがポインタを認識


# Page. 15

![Page Image](https://bcdn.docswell.com/page/VJPXW3QL78.jpg)

UPROPERTYを付けたUOｂjectのポインタについて
◆ C++コンパイラの立場からは普通のポインタとしてコンパイルされる
◆ ただし、UBT(Unreal Build Tool)がUPROPERTYの構文を解析し、
GENERATED_BODYマクロで生成されるコードによりGCから認識可能になる


# Page. 16

![Page Image](https://bcdn.docswell.com/page/2EVL8456JQ.jpg)

UObjectの生成
◆ NewObjectで生成するが、用途ごとのラッパーを介して生成する場合も多い
◆ アクターはSpawnActorで生成
◆ コンポーネントはCreateDefaultSubobjectで生成する事が多い
◆ エフェクトは SpawnEmitterAtLocation, SpawnEmitterAttachedで生成


# Page. 17

![Page Image](https://bcdn.docswell.com/page/57GX5162EL.jpg)

UObjectの破棄について
◆ UObjectの破棄の過程は二通りあります
a. 参照が無くなるとGCにより解放される（一般的なGCのイメージ）
b. 参照が残っていても、任意のタイミングで破棄する機構がある（Pending Kill）


# Page. 18

![Page Image](https://bcdn.docswell.com/page/4EQ8ZD49JP.jpg)

Pending Killについて
◆ 他から参照中でも任意のタイミングで破棄できる（MarkPendingKill）
その実現のため、Pending Killという中間状態が存在
◆ Pending KillのオブジェクトはいずれGCで解放される
GCによって解放されると参照中だったポインタにnullptrが自動で代入される


# Page. 19

![Page Image](https://bcdn.docswell.com/page/KJ423ZLRE1.jpg)

MarkPendingKillについて
◆ UObject::MarkPendingKillを呼ぶと、そのUObjectは「存在しないモノ」として
扱われる
◆ UObject::MarkPendingKillは通常直接コールしない
● アクタは、BPからはDestroyActor、C++からはDestroyを介して呼ぶ
● アクタコンポーネントは、DestroyComponentを介して呼ぶ
◆ この「参照していても破棄できる」仕組みは、コンテンツの都合で寿命を管
理したいゲーム制作において便利で、UObjectの設計の根幹と言える


# Page. 20

![Page Image](https://bcdn.docswell.com/page/LE1M1RLV7G.jpg)

GCによるメモリの解放
◆ UObjectは必ずGCを介して解放される
プログラマが特定のUObjectを自分で解放するコードは書けない
◆ GCによりメモリが解放されると同時に、当該オブジェクトを参照していた
全てのUPROPERTYにnullptrが代入される
◆ これにより、UPROPERTYのUOｂjectポインタはダングリングポインタが発
生しない仕組み


# Page. 21

![Page Image](https://bcdn.docswell.com/page/GEW381V3E2.jpg)

参照維持の仕組みを理解する
◆ 参照維持について、実務上気にするのは２点
● UObjectは生成しても参照を維持しないとGCによって突然解放されてしまう
● しかし、AActorとUActorComponentは意識して参照せずとも解放されることはない
◆ なぜそうなるのか
● RootSetと呼ばれるUObjectはGCされない
● RootSetが参照しているUObjectも同様にGCされない


# Page. 22

![Page Image](https://bcdn.docswell.com/page/47ZV8P4GE3.jpg)

RootSetから全てつながっている
….
….
◆ UWorldはRootSetとされていて、GCされない
◆ UWorldがULevelを参照、ULevelが以下全てを参照
◆ これでAActorとその下のリソースがGCから守られる


# Page. 23

![Page Image](https://bcdn.docswell.com/page/YJ65PM3Q7V.jpg)

UWorldがGCされない理由
◆ UWorldはAddToRootされ、RootSetとされることでGCされない
（全てのUObjectはAddToRoot出来るが、特殊な用途を除いて使用を避ける）


# Page. 24

![Page Image](https://bcdn.docswell.com/page/GJ5YKZL2J4.jpg)

UObjectのライフサイクルおさらい
◆ 任意タイミングで破棄する場合、Pending Killという中間状態を経由する
◆ GCによって解放されるとUPROPERTYで保持されたポインタはnullptrにな
る
※ 有効な参照が無いUObjectはPending Killを経由せず直接解放される


# Page. 25

![Page Image](https://bcdn.docswell.com/page/LE35Z9L1J5.jpg)

IsValid のすすめ
◆ 有効チェックは、
if (IsValid(Object)) とすべき
if (Object != nullptr) … では、PendingKillを見分けていない
ヌルチェックだけで絶対有効と確信があればいいが、迷ったらIsValid


# Page. 26

![Page Image](https://bcdn.docswell.com/page/8EDDRGNKEG.jpg)

BPのIsValid
◆ BPも同じIsValidを呼ぶ
BPでも参照前にIsValid関数で有効性をチェックする事により
NoneとPendingKillを区別する必要がなくなる


# Page. 27

![Page Image](https://bcdn.docswell.com/page/V7PXW353E8.jpg)

もし、別の場所でアクタが明示的に破棄されたら
◆ たとえば、OtherActorがどこかでDestroyActorされる場合、ANiceExample
からは以下の経緯を辿ることを観察できる
1. OtherActor != nullptr で、IsPendingKill() == false （生きている）
2. OtherActor != nullptr で、IsPendingKill() == true （DestroyActorされた）
3. OtherActor == nullptr （GCで回収された）


# Page. 28

![Page Image](https://bcdn.docswell.com/page/2JVL84PN7Q.jpg)

GCを強制的に起こすと...
◆ obj gcを入力すると、参照されていない、又はPendingKillのUObjectが即時
解放される
◆ この時、PendingKillのUObjectを参照している全てのUPROPERTYにnullptr
が代入される


# Page. 29

![Page Image](https://bcdn.docswell.com/page/5EGX51P5JL.jpg)

ここまでのまとめ
1. UObjectはRootSetを起点に数珠つなぎにすることでGCから守る
2. 参照数とは無関係に、任意のタイミングでUObjectを”Destroy”できる
3. PendingKillという、C++的に有効だがUE4の取り決めとして無効な状態が
ある
4. GCされると、UPROPERTYは自動でnullptrになる


# Page. 30

![Page Image](https://bcdn.docswell.com/page/4JQ8ZD2L7P.jpg)

使い方を間違った時の
バグの事例集


# Page. 31

![Page Image](https://bcdn.docswell.com/page/K7423ZL5J1.jpg)

【誤】 GCに参照無しとみなされ回収される
◆ UPROPERTYをつけないのは、２つの意味で誤り
１．参照保持されない
２．GC後ダングリングポインタになる


# Page. 32

![Page Image](https://bcdn.docswell.com/page/LJ1M1RL2EG.jpg)

【正】 UPROPERTYをつける
◆ UPROPERYTをつけると参照が保持され、GC実行後ダングリングポインタ
化もしない
※ 補足：自分で管理するUObject以外を参照する場合もUPROPERTYはつける。ダングリ
ングポインタ化する危険が伴うため。


# Page. 33

![Page Image](https://bcdn.docswell.com/page/GJW381V2J2.jpg)

【誤】 GCから見えないUPROPERTY
◆ ContainerにUPROPERTYがついていないと、その中の構造体の
UPROPERTYはGCから辿れないので、参照維持の能力がない


# Page. 34

![Page Image](https://bcdn.docswell.com/page/4EZV8P44J3.jpg)

【正】 GCから辿れるようにする
◆ ContainerにUPROPERTYをつけるとGCがMyObjectまで辿れるようになる


# Page. 35

![Page Image](https://bcdn.docswell.com/page/Y765PM3GEV.jpg)

【TIP】 ダングリングポインタを検知する方法
◆ IsValidLowLevelはダングリングポインタが疑われるUObjectを検証
falseを返したら、バグと判定できる
【注意1】デバッグ用であり、製品に組み込んではいけない！
【注意2】無効を有効と誤判定する可能性有り（GC後同一アドレスの別のUObjectが生ま
れた場合）


# Page. 36

![Page Image](https://bcdn.docswell.com/page/G75YKZLX74.jpg)

【誤】タイマーに生ポインタを渡す例
◆ この例はラムダ関数内でthisがダングリングポインタに


# Page. 37

![Page Image](https://bcdn.docswell.com/page/9J2GWRLQJR.jpg)

【正】 TWeakObjectPtrとしてUObjectを受け取る版のSetTimerを使う
◆ WorldTimerManagerに限らず、’DELEGATE’にはUObjectへのポインタを引数
に取るオーバーロード関数が用意されている
◆ thisはTWeakObjectPtrに変換され、タイマー呼び出し前のUObjectの寿命チェ
ックが行われる


# Page. 38

![Page Image](https://bcdn.docswell.com/page/DEYVLDQYEM.jpg)

【正】 UE4.22以上ならCreateWeakLambdaが使用可能
◆ ｔhisがTWeakObjectPtrとして寿命チェックされつつ、ラムダでキャプチャ
◆ コールバック用のUFUNCTIONを用意せずとも良くなり、コードが簡潔に
※ UE4.21にもCreateWeakLambdaの定義があるがコンパイルが通らない


# Page. 39

![Page Image](https://bcdn.docswell.com/page/VJNM46KR78.jpg)

【TIP】 TWeakObjectPtr
◆ いつ破棄されるかわからないUObjectを安全に参照するなら、生のポインタ（アドレス
値）の代わりにTWeakObjectPtrを使用（構造体の中などにも定義可能）
◆ Delegate、タイマー、コリジョンの実装でも使われている


# Page. 40

![Page Image](https://bcdn.docswell.com/page/YE9NQLVZE3.jpg)

【誤】デストラクタにコードを書く


# Page. 41

![Page Image](https://bcdn.docswell.com/page/GE8KGX4Y7D.jpg)

【正】 BeginDestroyかFinishDestroyに書く
◆ 安全を考えると、UObjectはデストラクタを実装すべきではない
◆ デストラクタ内でのみ、UPROPERTYがダングリングポインタになり得る
解放予定のメモリとして、nullptrを代入する事も省略されているため
◆ デストラクタの用途が必要なら、代わりにBeginDestroyかFinishDestroyを
実装する（UPROPERTYに安全にアクセス可能）


# Page. 42

![Page Image](https://bcdn.docswell.com/page/LELZG869JR.jpg)

【誤】 シングルトン
◆ RootSetから繋がっていないので、いずれGCに回収されてしまう
◆ MyInstanceの存在はGCが認知しないので、回収と同時にダングリングポ
インタになる


# Page. 43

![Page Image](https://bcdn.docswell.com/page/4JMVQ65VJW.jpg)

【正】 シングルトン
◆ 属するRootSetが無いので、シングルトン自身をAddToRoot


# Page. 44

![Page Image](https://bcdn.docswell.com/page/PJR28P3WE9.jpg)

シングルトンについて捕捉
◆ 上のAddToRootの例は、PIE/SIEの開始や終了とは無関係にシングルトンが存続
することに注意
◆ PIE/SIE中など、ゲーム中にのみ存在するシングルトンを意図する場合、
GameInstanceのUPROPETYで保持するなど、GameInstanceの寿命と連動させる


# Page. 45

![Page Image](https://bcdn.docswell.com/page/PEXD834V7X.jpg)

【誤】TSoftObjectPtr、レイジーロード後GCされる
◆ BeginPlayでロードさせたが、いつの間にかGCされる現象が発生する
（PIEでは回収されず生きていたりする為、スタンドアロンで発覚する事も）


# Page. 46

![Page Image](https://bcdn.docswell.com/page/3EKXKYZ57D.jpg)

【正】別途参照保持させるためのUPROPERTYを定義
◆ LoadSynchronousは、ロードはするがその後参照を保持することを意味しな
い
◆ 別途、参照保持を講じる


# Page. 47

![Page Image](https://bcdn.docswell.com/page/L735Z9D1E5.jpg)

【TIP】 TSoftObjectPtrとTWeakObjectPtrの違いは？
◆ どちらも弱参照（GCを防がない）
◆ TWeakObjectPtrはランタイムのUObjectを参照する
◆ TSoftObjectPtrはアセットに結びついている
ファイルのパスを保持している
⚫ UPROPERTY(EditDefaultsOnly) 等を指定して、エディタから設定できる
❖ UPROPERTYを書いてもGCを防げないことに注意！
⚫


# Page. 48

![Page Image](https://bcdn.docswell.com/page/87DDRGPK7G.jpg)

まとめ
◆ TWeakObjectPtrは参照先のUObjectが破棄されても安全
ポインタとしてUObjectのアドレス値だけ渡すのは危険
◆ デストラクタは書かないほうが良い
◆ TSoftObjectPtr は弱参照


# Page. 49

![Page Image](https://bcdn.docswell.com/page/VJPXW3N378.jpg)

全体のまとめ


# Page. 50

![Page Image](https://bcdn.docswell.com/page/2EVL84KNJQ.jpg)

UObjectについての全体のまとめ
◆ Unreal C++で書くと、new/deleteの出番はあまりなく、ほぼUObject
◆ C++らしくない独特の仕様と構文なので、学習コストは掛かる
◆ ゲーム制作に特化
◆ 慣れると少ないコードで効率的かつ安全にリソースを管理できる


# Page. 51

![Page Image](https://bcdn.docswell.com/page/57GX51Q5EL.jpg)

おまけ


# Page. 52

![Page Image](https://bcdn.docswell.com/page/4EQ8ZDKLJP.jpg)

BeginDestroyとFinishDestroyに分かれている意味は？
◆ 主にグラフィックリソースの管理に利用されている
◆ BeginDestroyはすぐ実行される
◆ レンダースレッドやGPUが参照しているリソースはすぐに捨てられない為、
解放が可能な時まで待ってからFinishDestroyが実行される


# Page. 53

![Page Image](https://bcdn.docswell.com/page/KJ423ZP5E1.jpg)

PendingKillかどうかデバッガから見たい
◆ InternalIndexを65536で割り、
GUObjectArray.ObjObjects.Objects[&lt;商&gt;][&lt;余り&gt;] をWatchに追加
デバッガを16進表示にして上4ケタを&lt;商&gt;、下4ケタを&lt;余り&gt;の位置に入力
正しく入力できると“Object”が対象のオブジェクトを指す事が確認できる
“Flags”に0x20000000のビットがあればPendingKill（EInternalObjectFlags）
エディタバイナリの場合はモジュール(dll)が分かれているので、デバッガには
UE4Editor-CoreUObject.dll!GUObjectArray.ObjObjects.Objects と入力


# Page. 54

![Page Image](https://bcdn.docswell.com/page/LE1M1R927G.jpg)

シリアルが桁あふれしたら？
◆ TWeakObjectPtrがUObjectの同一性チェックに使うシリアルは32ビット
◆ あふれるとFatalで停止する
◆ ただし、全てのUObjectにシリアルが割当たるわけではなく、初めて
TWeakObjectPtrに参照された瞬間に割り当たる
アクタ、コンポーネント等は無条件に割り当たる
◆ 十分に大きい値であるので、桁あふれは想定せずとも良いかもしれない
（現実的な起動時間内に桁あふれを起こす頻度でUObjectを作る事による性能低下が先に問題になると思われる）


# Page. 55

![Page Image](https://bcdn.docswell.com/page/GEW381Q2E2.jpg)

AddReferencedObjectsで参照を維持
◆ UPROPERTYを書かず参照を維持する方法
◆ UPROPERTY同様、破棄されたらnullptrを代入してくれる


# Page. 56

![Page Image](https://bcdn.docswell.com/page/47ZV8PW4E3.jpg)

デバッグ用にアロケータを変えてみる
◆ 不正なメモリアクセスを疑ったら、-stompmalloc オプション
UE4はデフォルトで独自の高速なアロケータを使う
効率は良いが、不正なメモリアクセスが起きた時点で例外を発生させられない
（クラッシュ箇所がバグの原因ではない事がしばしば）
◆ -stompmalloc １アロケート毎にOSにページを要求する
https://pzurita.wordpress.com/2015/06/29/memory-stomp-allocator-for-unreal-engine-4/
◆ -stompmalloc が重すぎるなら -ansimalloc で問題箇所を捕捉できる場合も
malloc/freeを直接使うようになる


# Page. 57

![Page Image](https://bcdn.docswell.com/page/YJ65PMGG7V.jpg)

-stompmallocは結構重い
ThirdPersonサンプルを-stompmallocオプションで開き、タスクマネージャで確認
-stompmallocなし
-stompmallocあり


