Unreal スマート ポインタ ライブラリ は、メモリ割り当てとメモリ追跡の負担を軽減するために設計された C++11 のスマート ポインタをカスタム実装したものです。この実装には、業界標準の シェアード ポインタ 、弱いポインタ 、および ユニーク ポインタ が含まれています。non-nullable 型のシェアード ポインタとして動作する 共有の参照 も追加されています。これらのクラスを UObject
システムで使用することはできません。Unreal オブジェクトはゲーム コード向けに改良された独自のメモリ追跡システムを使用しているためです。
スマート ポインタのタイプ
スマート ポインタは、スマート ポインタに含まれる、または参照されるオブジェクトのライフスパンに影響します。スマート ポインタのタイプによって、オブジェクトに対する制限と効果が異なります。次の表は、各スマート ポインタのタイプをどの状況で使用するのが適切かを判断するうえで役立ちます。
スマート ポインタのタイプ | 使用事例 |
---|---|
シェアード ポインタ (TSharedPtr ) |
シェアード ポインタは、参照するオブジェクトを所有することで、そのオブジェクトが削除されるのを永続的に防ぎます。また、最終的には、シェアード ポインタまたは共有の参照 (下記を参照) から参照されなくなったら、そのオブジェクトを削除します。シェアード ポインタは空 (すなわち、オブジェクトを一切参照しない) であることもできます。null ではないシェアード ポインタはすべて、参照するオブジェクトへの共有の参照を生成できます。 |
共有の参照 (TSharedRef ) |
共有の参照は、参照するオブジェクトを所有するという意味では、シェアード ポインタと同様に機能します。null オブジェクトに関しては異なり、共有の参照は必ず null 以外のオブジェクトを参照する必要があります。シェアード ポインタにはこのような制約がないため、共有の参照は常にシェアード ポインタに変換でき、そのシェアード ポインタは有効なオブジェクトを参照することが保証されます。参照されているオブジェクトが null 以外であることを保証したい、または共有オブジェクトの所有権を示したい場合は共有の参照を使用してください。 |
弱いポインタ (TWeakPtr ) |
弱いポインタはシェアード ポインタに似ていますが、参照するオブジェクトを所有しないため、オブジェクトのライフサイクルに影響を与えません。この特性は、参照サイクルを中断する場合に大変役立ちます。ただし、弱いポインタが警告を表示することなくいつでも null になることがあるということも意味します。このため、弱いポインタは参照するオブジェクトへのシェアード ポインタを生成することで、プログラマーが一時的にオブジェクトに安全にアクセスできるようにします。 |
ユニーク ポインタ (TUniquePtr ) |
ユニーク ポインタは単に参照するオブジェクトを明示的に所有します。特定のリソースに対して設定できるユニーク ポインタは 1 つのみであるため、ユニーク ポインタは所有権を転送できても共有することができません。ユニーク ポインタを複製しようとしてもコンパイル エラーになります。ユニーク ポインタがスコープ外になると、参照しているオブジェクトが自動的に削除されます。 |
ユニーク ポインタが参照するオブジェクトへのシェアード ポインタまたは共有の参照を作ることはリスクを伴います。このため、他のスマート ポインタが引き続きオブジェクトを参照していたとしても、ユニーク ポインタの破壊時に、ユニーク ポインタによるオブジェクトの削除の動作は中断されません。同様に、シェアード ポインタまたは共有の参照が参照するオブジェクトへのユニーク ポインタを作成しないでください。
スマート ポインタの利点
利点 | 説明 |
---|---|
メモリ リークの防止 | スマート ポインタ (弱いポインタ以外) は、共有の参照がなくなると、オブジェクトを自動的に削除します。 |
弱い参照 | 弱いポインタは参照サイクルを中断するため、ダングリング ポインタの発生を防ぎます。 |
オプションのスレッド セーフティ | Unreal スマート ポインタ ライブラリには複数のスレッドにまたがって参照カウントを管理するスレッド セーフなコードが含まれます。スレッド セーフティが不要な場合は、使用せずにパフォーマンスを向上させて構いません。 |
ランタイムの安全性 | 共有の参照は null であることはないため、常に逆参照できます。 |
意図がわかる | オブザーバーから簡単にオブジェクトの所有者がわかります。 |
メモリ | スマート ポインタは 64 ビット環境の C++ ポインタ サイズのわずか 2 倍です (加えて共有の 16 バイト参照コントローラー)。ただし、C++ ポインタと同じサイズのユニーク ポインタは例外です。 |
ヘルパー クラスと関数
スマート ポインタを簡単かつ直観的に使用するために、Unreal スマート ポインタ ライブラリには、ヘルパー クラスとヘルパー関数がいくつか用意されています。
ヘルパー | 説明 |
---|---|
クラス | |
TSharedFromThis |
クラスを TSharedFromThis から派生させると、AsShared 関数または SharedThis 関数が追加されます。これらの関数によって、オブジェクトに TSharedRef を取得できます。 |
機能 | |
MakeShared および MakeShareable |
通常の C++ ポインタからシェアード ポインタを生成します。MakeShared は新規オブジェクト インスタンスと参照コントローラーを単一のメモリ ブロックに割り当てますが、パブリック コンストラクタを提供するオブジェクトが必要です。MakeShareable は効率性が低下しますが、オブジェクトのコンストラクタがプライベートである場合でも動作し、オブジェクトを作成していなくても所有権を取得することができ、そのオブジェクトの削除時にカスタマイズした動作がサポートされます。 |
StaticCastSharedRef と StaticCastSharedPtr |
静的に型変換 (キャスト) するユーティリティ関数です。一般的に派生した型のダウンキャストに使用します。 |
ConstCastSharedRef と ConstCastSharedPtr |
const のスマート参照またはスマート ポインタをそれぞれ mutable のスマート参照またはスマート ポインタに変換します。 |
スマート ポインタの実装に関する詳細
Unreal スマート ポインタ ライブラリのスマート ポインタは、機能性と効率性の面でいくつかの一般的特徴を共有しています。
速度
スマート ポインタの使用を検討する際は、常にパフォーマンスの考慮が重要となります。スマート ポインタは、特定のハイレベルなシステム、リソース管理、またはツール プログラミングに適しています。ただし、スマート ポインタのタイプによっては、生の C++ ポインタよりも低速となり、このオーバーヘッドにより、レンダリングなどの低レベルのエンジン コードにおける有用性が低下します。
スマート ポインタのパフォーマンス上の利点は以下のとおりです。
-
すべての操作を一定時間で実行
-
ほとんどのスマート ポインタは逆参照すると生の C++ ポインタと同等に高速 (出荷ビルドの場合)
-
スマート ポインタをコピーしてもメモリが割り当てられない
-
スレッド セーフなスマート ポインタはロックフリー
スマート ポインタのパフォーマンス上の欠点は以下のとおりです。
-
生の C++ ポインタを作成したりコピーするよりスマート ポインタを作成、コピーしたときのオーバーヘッドの方が大きい
-
参照カウントを維持すると基本演算のサイクルが増える
-
スマート ポインタによっては、C++ ポインタよりも多くのメモリを使用することがある
-
参照コントローラーには 2 つのヒープ割り当てが存在する
MakeShareable
の代わりにMakeShared
を使用して 2 つ目の割り当てを回避することで、パフォーマンスを向上させることができます。
侵入型アクセサ
シェアード ポインタは非侵入型です。つまり、スマート ポインタによって所有されているかどうかについてオブジェクトは認識しません。これは、通常は許容されますが、共有の参照またはシェアード ポインタとしてオブジェクトにアクセスしたい場合があるかもしれません。そうするためには、オブジェクトのクラスをテンプレート パラメータとして使用して、TSharedFromThis
からオブジェクトのクラスを派生させます。TSharedFromThis
は、オブジェクトを共有の参照に (そしてそこからシェアード ポインタへ) 変換できる 2 つの関数 AsShared
と SharedThis
を提供します。これは、常に共有の参照を返すクラス ファクトリ、または、オブジェクトを共有の参照またはシェアード ポインタを想定するシステムに渡す必要がある場合に役立ちます。AsShared
は、本来 TSharedFromThis
へのテンプレート引数として渡される型としてクラスを返します。この型は呼び出し側のオブジェクトの親の型となる場合がある一方で、SharedThis
はこれから直接型を派生させて、その型のオブジェクトを参照するスマート ポインタを返します。以下のサンプル コードで両方の関数を示します。
class FRegistryObject;
class FMyBaseClass: public TSharedFromThis<FMyBaseClass>
{
virtual void RegisterAsBaseClass(FRegistryObject* RegistryObject)
{
// 「this」への共有の参照にアクセスします。
// ここでは直接 <TSharedFromThis> から継承したので、AsShared() と SharedThis(this) が同じ型を返します。
TSharedRef<FMyBaseClass> ThisAsSharedRef = AsShared();
// RegistryObject は TSharedRef<FMyBaseClass> または TSharedPtr<FMyBaseClass> を想定しています。TSharedRef は暗黙的に TSharedPtr に変換されることがあります。
RegistryObject->Register(ThisAsSharedRef);
}
};
class FMyDerivedClass : public FMyBaseClass
{
virtual void Register(FRegistryObject* RegistryObject) override
{
// ここでは TSharedFromThis<> から直接継承しなかったため、AsShared() と SharedThis(this) が異なる型を返します。
// AsShared() は TSharedFromThis<> で本来指定した型を返します。この例では TSharedRef<FMyBaseClass> です。
// SharedThis(this) は「this」の型を含む TSharedRef を返します。この例では TSharedRef<FMyDerivedClass> です。
// SharedThis() 関数は「this」ポインタと同一スコープ内でのみ利用可能です。
TSharedRef<FMyDerivedClass> AsSharedRef = SharedThis(this);
// FMyDerivedClass が FMyBaseClass の型の一種なので、RegistryObject は TSharedRef<FMyDerivedClass> を受け入れます。
RegistryObject->Register(ThisAsSharedRef);
}
};
class FRegistryObject
{
// この関数は FMyBaseClass またはその子クラスへの TSharedRef または TSharedPtr を受け入れます。
void Register(TSharedRef<FMyBaseClass>);
};
この時点で共有の参照は初期化されていないので、コンストラクタから AsShared
や SharedThis
を呼び出さないでください。クラッシュやアサートが発生します。
キャスト
Unreal スマート ポインタ ライブラリに含まれるサポート関数をいくつか使用して、シェアード ポインタ (および共有の参照) をキャストできます。アップキャストは C++ ポインタと同様、暗黙的に行われます。ConstCastSharedPtr
関数でコンスト キャスト、StaticCastSharedPtr
関数で静的キャスト (しばしば派生したクラス ポインタのダウンキャストに使用されます) が可能です。実行時型情報 (RTTI) が存在しないので、動的キャストはサポートされません。代わりに、以下のコードのように静的キャストを使用してください。
// ここでは、他の手段で FDragDropOperation が実際は FAssetDragDropOp であることを検証済みであると想定します。
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
// StaticCastSharedPtr でキャスト可能になりました。
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
スレッド セーフティ
デフォルトでは、スマート ポインタは単一スレッドにアクセスする場合のみ安全です。複数のスレッドへのアクセスが必要な場合、スマート ポインタ クラスのスレッド セーフなバージョンを使用します。
-
TSharedPtr<T, ESPMode::ThreadSafe>
-
TSharedRef<T, ESPMode::ThreadSafe>
-
TWeakPtr<T, ESPMode::ThreadSafe>
-
TSharedFromThis<T, ESPMode::ThreadSafe>
これらのスレッド セーフなバージョンはアトミックな参照カウントにより若干低速ですが、動作は通常の C++ ポインタと一致します。
-
Read と Copy は常にスレッド セーフです。
-
Write と Reset は安全性のため同期する必要があります。
ポインタが 1 つ以上のスレッドからアクセスされることがないとわかっている場合、スレッド セーフ バージョンの使用を避けた方がパフォーマンスが向上します。
ヒントと制限事項
-
逆参照や参照カウントによってオーバーヘッドが発生するため、可能な限り
TSharedRef
やTSharedPtr
パラメータとしてデータを関数に渡すことは避けてください。代わりに、参照されているオブジェクトを「const &
」として渡すことを推奨します。 -
シェアード ポインタを前方宣言して不完全な型にすることができます。
-
シェアード ポインタは Unreal オブジェクト (
UObject
およびその派生クラス) と互換性がありません。Unreal Engine にはUObject
管理のための独自のメモリ管理システムがあります (「Unreal でのオブジェクトの処理」 を参照)。これら 2 つのシステムは互いに干渉しません。