9
3

More than 1 year has passed since last update.

UnrealEngine UPROPERTY()の重要な役割とIsValid()の使い所

Posted at

はじめに

クラスのメンバ変数を定義するときにメンバ変数の上にこういうコードを書いたことがあると思います。

	UPROPERTY(EditAnywhere)
	int32 Health;

今日はUPROPERTY()の大事な役割について話します。これを付け忘れると大変困ったことになるんです。

BPに公開するためだけじゃない

UPROPERTY()は プロパティ指定子 を定義するのに使うことが多いと思います。

EditAnywhere、BlueprintReadWrite
などはよく使うのではないでしょうか?これらの説明はドキュメント見てもらうとして、
今回はポインタ変数に付けるUPROPERTY()について説明します。

	UPROPERTY()
	AActor* Weapon;

	UPROPERTY()
	UParticleSystemComponent* Effect;

こういうやつです。

結論から言うと
「UObject継承のクラスのポインタ変数にはUPROPERTY()を付け忘れてはいけない」 です。

付け忘れるとどうなるの?

UPROPERTY()はポインタ変数に付けた場合に以下ような機能があります。

  • ポインタの参照カウンタを増やす
  • DestroyされたらNullポインタにしてくれる

そのため、
どのクラスにも参照されていないと判断されてGC(Garbage Collect)されてしまいます。
デフォルトだと開始1分後にGCが走るため、ちょうど1分後くらいにクラッシュしたりします。

ポインタの指している先が急になくなるので当然ですね。
しかも1分後というのが発見しずらい、いやらしいバグになります。

あと、ActorなどはDestroy()することで強制的に削除することができます。
その場合にも同じように解放済みのメモリを参照してしまいクラッシュの原因になります。

試しにやってみよう

参照カウンタの確認

意味は無いですがUCanvasをNewObjectで作ってみましょう。
UPROPERTY()を付けないとどうなるか見てみます。

test.h
	UCanvas* Canvas = nullptr;
test.cpp
    Canvas = NewObject<UCanvas>();

作った直後
image.png

"obj gc" コマンドで強制的にGCを走らせます

image.png
消えちゃいましたね。

次に、UPROPERTY()を付けてやってみます

	UPROPERTY()
	UCanvas* Canvas = nullptr;

"obj gc"を実行しても消えませんでした。
image.png

Nullにしてくれるか確認

ActorをSpawnActorして1秒後にDestroyしてNullにしてくれるか確認してみましょう。

test.h
	UPROPERTY()
	AActor* Actor = nullptr;
test.cpp
void APropertyTestGameModeBase::BeginPlay()
{
	Super::BeginPlay();
	Actor = GetWorld()->SpawnActor<AActor>();

	FTimerHandle handle;
	GetWorldTimerManager().SetTimer(handle, this, &APropertyTestGameModeBase::DestroyObject, 1.0f);
}

void APropertyTestGameModeBase::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	UE_LOG(LogTemp, Log, TEXT("%x"), Actor);
}

実行してログをみてみましょう。
image.png
・・・あれ?・・・Nullになりませんね。GCしてみましょう。

image.png

Nullになりました!DestroyしてもGCされないと消えない、ポインタも消えるまでNullにはしてくれないようです。

死にかけのActorは参照したくないんだけど

そいうあなたにはIsValid()がおすすめです。

    if (Actor)
    {
        // Destroy後の死にかけの場合も処理されちゃう
    }

    if (IsValid(Actor))
    {
        // Destroyされたら処理されない
    }

全部IsValidでいいじゃん。

って声が聞こえてきそうですが、IsValid()はnullチェックに比べて多少のオーバーヘッドがあります。
なので自分は基本はnullチェックだけど死にかけのActorで参照しちゃまずい場合にIsValidを使うことにしています。

UPROPERTY()の付け忘れ、実はReSharperは教えてくれる

image.png
「オブジェクト メンバー "Weapon" はいつでもガベージ コレクションできます。」
こっそり水色波線で教えてくれてました。ReSharper!便利!素敵!

おちいりやすい罠

構造体のメンバにはUPROPERTY()付けたけどその構造体をメンバ変数として定義するときにUPROPERTY()付けてない場合
image.png
こういう場合もアウトです。詳しくは以下のスライドをご参照下さい。

UE5からお作法が変わります

Actor.h
	UPROPERTY()
	TObjectPtr<USceneComponent> RootComponent;

生ポインタではなくTObjectPtrを使う事が推奨されています。

TObjectPtrって何?

今後実装される機能で

  • オブジェクトの依存関係の追跡
  • エディターでのオブジェクトの遅延読み込み

などがあり、それに使われるようです。
パッケージなどエディタ以外のビルドでは生ポインタに変換されるそうです。

おわりに

ポインタ変数には UPROPERTY() これだけは忘れないで下さいね。

以上。

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3