TArray
の後、Unreal Engine で最もよく使用されているコンテナが TMap
です。TMap
は、ハッシュ キーに基づく構造体である点で TSet
に似ています。しかし、TSet
とは異なり、このコンテナはデータをキーと値のペア (TPair<KeyType, ValueType>
) として格納します。キーは格納と取得のためだけに使用されます。
マップには 2 タイプあります。TMap
と TMultiMap
です。これら 2 つの違いは、TMap
のキーは一意であるのに対し、TMultiMap
は複数の同一のキーの格納をサポートしていることです。既存のペアと一致するキーを使用して TMap
に新たにキーと値のペアを追加するとき、新しいペアによって古いペアが置き換えられます。TMultiMap
では、コンテナに新しいペアと古いペアの両方が格納されます。
TMap
TMap
では、キーと値のペアが個別のオブジェクトであるかのように、各ペアがマップの要素の型として扱われます。このドキュメントでは、「要素」とはキーと値のペアを指し、個々のコンポーネントは、「要素のキー」または「要素の値」と呼びます。要素の型は実際は TPair<KeyType, ElementType>
ですが、TPair
型を直接言及する必要があることはほとんどありません。
TArray
と同様に、TMap
は均一なコンテナです。つまり、要素はすべて、厳密に同じ型です。TMap
は値型でもあり、通常のコピー、代入、デストラクタの操作をサポートしています。また、要素の強力な所有権をサポートしているため、マップが破棄されたら値も破棄されます。キーと値も値型です。
TMap
はハッシュ コンテナであり、キーの型は GetTypeHash
をサポートし、キーを比較して同一かどうかを確認するための operator==
を提供する必要があります。ハッシュについては、後で詳しく説明します。
TMap
はオプションでアロケータを受け取って、メモリ割り当て動作を制御することもできます。しかし、TArray
とは異なり、FHeapAllocator
や TInlineAllocator
などの標準の UE4 アロケータとは異なるセット アロケータがあります。セット アロケータ (クラス TSetAllocator
) は、マップで使用するハッシュ バケット数と、ハッシュと要素の格納に使用する標準 UE4 アロケータを定義します。
最後の TMap
テンプレート パラメータは KeyFuncs
です。これは、マップが要素の型からキーを取得する方法、2 つのキーが同一かどうかを比較する方法、およびキーをハッシュする方法を指示します。これらにはデフォルト設定があります。デフォルトでは、参照をキーに戻し、同一かどうかの比較に operator==
を使用し、ハッシュには非メンバー関数の GetTypeHash
を使用します。キーの型がこれらの関数をサポートしている場合、カスタム KeyFuncs
を指定しなくても、それをマップ キーとして使用できます。
TArray
とは異なり、メモリ内における TMap
要素の相対的順序は信頼できません (安定していません)。要素を繰り返し処理すると、要素を追加した順序とは異なる順序で要素が返される可能性があります。また、要素がメモリ内で隣り合うように配置されない可能性もあります。マップのバッキング データ構造はスパース行列です。これは、要素間のギャップを効果的にサポートする配列です。マップから要素が削除されると、スパース行列にギャップが発生します。新しい要素を配列に追加することで、そのギャップを埋めることができます。しかし、TMap
ではギャップを埋めるために要素をシャッフルしませんが、マップ要素へのポインタは引き続き無効化されている場合があります。ストレージがいっぱいのときに新しい要素を追加する場合、ストレージ全体が再割り当てされることがあるからです。
マップを作成および埋める
TMap
は次のように作成できます。
TMap<int32, FString> FruitMap;
FruitMap
は現在、整数キーで識別される文字列の空の TMap
です。アロケータと KeyFuncs
のいずれも指定していないので、マップは標準的なヒープ割り当てを行い、operator==
を使用してキー (int32
型) を比較し、GetTypeHash
を使用してハッシュします。この時点で、メモリは割り当てられません。
マップを埋める標準的な方法は、キーと値を指定して Add
を呼び出すことです。
FruitMap.Add(5, TEXT("Banana"));
FruitMap.Add(2, TEXT("Grapefruit"));
FruitMap.Add(7, TEXT("Pineapple"));
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Grapefruit" },
// { Key: 7, Value: "Pineapple" }
// ]
ここで要素は挿入順にリストされていますが、この順番がメモリ内の実際の順番である保証はありません。新しいマップの場合、挿入順である可能性が高いですが、挿入や削除が多数行われる場合は、新しい要素が最後に表示される可能性はいっそう低くなります
これは TMultiMap
ではないので、キーは一意であると保証されています。以下に、重複するキーを追加しようとした結果を示します。
FruitMap.Add(2, TEXT("Pear"));
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" }
// ]
マップに含まれる要素数は 3 つで変わりませんが、キー 2 の値は以前 "Grapefruit" だったところ、"Pear" に置き換えられています。
Add
関数は、値が指定されていないキーを受け取ることもできます。このオーバーロードされた Add
が呼び出されると、値はデフォルト コンストラクタの動作に従います。
FruitMap.Add(4);
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "" }
// ]
マップへの挿入時に一時要素が作成されるのを避けるために、TArray
と同様に、Add
の代わりに Emplace
を使用することができます。
FruitMap.Emplace(3, TEXT("Orange"));
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "" },
// { Key: 3, Value: "Orange" }
// ]
ここでは、キーと値がそれぞれの型のコンストラクタに直接渡されています。int32
のキーは何も影響を受けませんが、その値に一時的な FString
が作成されないようにしています。TArray
とは異なり、引数が 1 つのコンストラクタでのみ要素をマップに配置可能です。
Append
関数を使用して 2 つのマップをマージすることもできます。マージすると、一方のマップの要素がすべてもう一方のマップに移動します。
TMap<int32, FString> FruitMap2;
FruitMap2.Emplace(4, TEXT("Kiwi"));
FruitMap2.Emplace(9, TEXT("Melon"));
FruitMap2.Emplace(5, TEXT("Mango"));
FruitMap.Append(FruitMap2);
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
// FruitMap2 is now empty.
上記の例では、結果のマップは Add
または Emplace
を使用して FruitMap2
の各要素を個別に追加し、処理の完了時に FruitMap2
を空にしたものと同一です。つまり、FruitMap
にすでに存在する要素のうち、キーが FruitMap2
の要素と同一の要素は置き換えられます。
TMap
を UPROPERTY
マクロと「編集可能」なキーワード (EditAnywhere
、EditDefaultsOnly
、または EditInstanceOnly
) のいずれかでマークすると、エディタ内で要素の追加と編集 ができます。
UPROPERTY(Category = MapsAndSets, EditAnywhere)
TMap<int32, FString> FruitMap;
イタレーション
TMaps
のイタレーションは TArrays
に似ています。要素の型が TPair
であることを思い出せば、C++ の ranged-for 機能を利用できます。
for (auto& Elem :FruitMap)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
Elem.Key,
*Elem.Value
)
);
}
// Output:
// (5, "Mango")
// (2, "Pear")
// (7, "Pineapple")
// (4, "Kiwi")
// (3, "Orange")
// (9, "Melon")
CreateIterator
および CreateConstIterators
関数を使用してイタレータを作成することもできます。CreateIterator
は読み取り/書き込みアクセスができるイタレータを返しますが、CreateConstIterator
は読み取り専用イタレータを返します。いずれの場合も、それらのイタレータの Key
および Value
関数を使用して、要素を調査できます。イタレータを使用して例の「フルーツ」マップのコンテンツを出力すると、次のようになります。
for (auto It = FruitMap.CreateConstIterator(); It; ++It)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
It.Key(), // same as It->Key
*It.Value() // same as *It->Value
)
);
}
クエリ
マップ内の現在の要素数を確認するには、Num
関数を呼び出します。
int32 Count = FruitMap.Num();
// Count == 6
マップに特定のキーが含まれているかどうかを判断するには、次のように Contains
関数を呼び出します。
bool bHas7 = FruitMap.Contains(7);
bool bHas8 = FruitMap.Contains(8);
// bHas7 == true
// bHas8 == false
マップに特定のキーが含まれていることが分かっている場合、operator[]
でそのキーをインデックスとして使用して、対応する値を検索できます。これを非定数マップで行うと、非定数の参照が返されます。一方、定数マップでは定数の参照が返されます。
operator[]
を使用する前には、マップにそのキーが含まれていることを確認してください。マップにそのキーが含まれていない場合、アサートが発生します。
FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
FString Val8 = FruitMap[8];
// Assert!
マップにキーが含まれているかどうか不明な場合は、Contains
関数で確認してから operator[]
を使用します。しかし、この方法は最適ではありません。取得を正常に行うには、同じキーに対して 2 回の検索を行う必要があるからです。Find
関数は、その 2 つの動作を 1 回の検索にまとめたものです。Find
は、マップにキーが含まれる場合、その要素の値へのポインタを返し、キーが含まれない場合は null ポインタを返します。定数マップで Find
を呼び出すと、返されるポインタも定数になります。
FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
// Ptr8 == nullptr
クエリで有効な結果を取得する別の方法は、FindOrAdd
または FindRef
を使用することです。FindOrAdd
は、指定したキーに関連付けられた値への参照を返します。そのキーがマップに含まれない場合、FindOrAdd
は新しく作成した要素を返します。その要素は、指定したキーとデフォルト コンストラクタによる値が設定され、マップにも追加されます。この関数はマップを変更する可能性があるため、FindOrAdd
は非定数マップでしか利用できません。FindRef
は、その名前に反して、キーに関連付けられた値のコピー、またはキーがマップ内にない場合はデフォルト コンストラクタによる値を返します。FindRef
は新しい要素を作成しないので、定数マップと非定数マップの両方で利用できます。FindOrAdd
と FindRef
はキーがマップ内にない場合も正常に実行できるため、Contains
による事前確認や、戻り値の null 確認といった通常の安全手順を行わなくても、安全に呼び出すことができます。
FString& Ref7 = FruitMap.FindOrAdd(7);
// Ref7 == "Pineapple"
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
FString& Ref8 = FruitMap.FindOrAdd(8);
// Ref8 == ""
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" },
// { Key: 8, Value: "" }
// ]
FString Val7 = FruitMap.FindRef(7);
FString Val6 = FruitMap.FindRef(6);
// Val7 == "Pineapple"
// Val6 == ""
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" },
// { Key: 8, Value: "" }
// ]
FindOrAdd
はマップに新しいエントリを追加する場合があるため (例では Ref8
の初期化時)、以前取得した (Find
からの) ポインタや (FindOrAdd
からの) 参照が無効になることがあります。これは、追加操作のときに、新しい要素を含めるためにマップのバックエンド ストレージを拡張する必要がある場合に、メモリを割り当てて既存データを移動した結果です。上記の例では、FindOrAdd(8)
の呼び出しの後の Ref8
の後に Ref7
が無効化される可能性があります。
FindKey
関数は逆引き参照を行います。つまり、指定された値がキーに一致したら、指定された値とペアにされている最初のキーへのポインタを返します。マップに存在しない値を検索した場合は、null キーを返します。
const int32* KeyMangoPtr = FruitMap.FindKey(TEXT("Mango"));
const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat"));
// *KeyMangoPtr == 5
// KeyKumquatPtr == nullptr
値による検索は、キーによる検索より低速 (線形時間) です。それは、マップが値ではなくキーによってハッシュされているからです。さらに、マップに同じ値を持つキーが複数存在している場合、FindKey
はその中のいずれかを返します。
GenerateKeyArray
および GenerateValueArray
関数は、すべてのキーと値のそれぞれのコピーで TArray
を埋めます。いずれの場合も、渡される配列は空にされてから埋められるため、結果の要素数はマップの要素数と常に同一です。
TArray<int32> FruitKeys;
TArray<FString> FruitValues;
FruitKeys.Add(999);
FruitKeys.Add(123);
FruitMap.GenerateKeyArray (FruitKeys);
FruitMap.GenerateValueArray(FruitValues);
// FruitKeys == [ 5,2,7,4,3,9,8 ]
// FruitValues == [ "Mango","Pear","Pineapple","Kiwi","Orange",
// "Melon","" ]
削除
Remove
関数を使用し、削除する要素のキーを指定することで、マップから要素を削除できます。戻り値は削除された要素数です。キーに一致する要素がマップに含まれていない場合は、0 が戻ります。
FruitMap.Remove(8);
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
要素を削除すると、データ構造に穴が残る場合があります。これは、Visual Studio のウォッチ ウィンドウでマップを視覚化して確認できますが、ここでは分かりやすくするために省略しています。
FindAndRemoveChecked
関数を使用して、マップから要素を削除し、その値を返すことができます。関数名の "checked" は、キーが存在しない場合にマップで check
が呼び出される (UE4 で assert
に相当) ことを意味します。
FString Removed7 = FruitMap.FindAndRemoveChecked(7);
// Removed7 == "Pineapple"
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
FString Removed8 = FruitMap.FindAndRemoveChecked(8);
// Assert!
RemoveAndCopyValue
関数は Remove
に似ていますが、削除された要素の値を参照パラメータにコピーします。指定したキーがマップに存在しない場合は、出力パラメータは変化せず、関数が false
を返します。
FString Removed;
bool bFound2 = FruitMap.RemoveAndCopyValue(2, Removed);
// bFound2 == true
// Removed == "Pear"
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
bool bFound8 = FruitMap.RemoveAndCopyValue(8, Removed);
// bFound8 == false
// Removed == "Pear", i.e. unchanged
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
最後に紹介するのは Empty
関数と Reset
関数です。これらの関数を使用して、マップからすべての要素を削除できます。
TMap<int32, FString> FruitMapCopy = FruitMap;
// FruitMapCopy == [
// { Key: 5, Value: "Mango" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
FruitMapCopy.Empty(); // ここで Reset() を呼び出すこともできる。
// FruitMapCopy == []
Empty
と Reset
は似ていますが、Empty
はマップ内に残すスラック数を示すパラメータを受け取ることができるのに対し、Reset
は常にできる限り多くのスラックを残します。
ソート
TMap
はソートができます。ソート後、マップをイタレーションすると、ソートした順番で要素が表示されますが、この動作が保証されるのは、次にマップに変更を加える時点までです。ソートは不安定なため、MultiMap 内の同等の要素がさまざまな順番で表示される可能性があります。
KeySort
または ValueSort
関数を使用して、キーまたは値でそれぞれソートすることができます。いずれの関数も、ソート順を指定するバイナリ述語を受け取ります。
FruitMap.KeySort([](int32 A, int32 B) {
return A > B; // sort keys in reverse
});
// FruitMap == [
// { Key: 9, Value: "Melon" },
// { Key: 5, Value: "Mango" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" }
// ]
FruitMap.ValueSort([](const FString& A, const FString& B) {
return A.Len() < B.Len(); // sort strings by length
});
// FruitMap == [
// { Key: 4, Value: "Kiwi" },
// { Key: 5, Value: "Mango" },
// { Key: 9, Value: "Melon" },
// { Key: 3, Value: "Orange" }
// ]
演算子
TArray
と同様に、TMap
は通常の値型であり、標準的なコピー コンストラクタまたは代入演算子でコピーできます。マップは必ず要素を所有しているため、マップのコピーは深いコピーです。新しいマップには固有の要素のコピーがあります。
TMap<int32, FString> NewMap = FruitMap;
NewMap[5] = "Apple";
NewMap.Remove(3);
// FruitMap == [
// { Key: 4, Value: "Kiwi" },
// { Key: 5, Value: "Mango" },
// { Key: 9, Value: "Melon" },
// { Key: 3, Value: "Orange" }
// ]
// NewMap == [
// { Key: 4, Value: "Kiwi" },
// { Key: 5, Value: "Apple" },
// { Key: 9, Value: "Melon" }
// ]
TMap
はムーブ セマンティクスをサポートしています。これは、MoveTemp
関数を使用して呼び出すことができます。ムーブの後、ソース マップは空であることが保証されます。
FruitMap = MoveTemp(NewMap);
// FruitMap == [
// { Key: 4, Value: "Kiwi" },
// { Key: 5, Value: "Apple" },
// { Key: 9, Value: "Melon" }
// ]
// NewMap == []
スラック
スラックは、要素を含まない割り当てメモリです。Reserve
を呼び出すことで、要素を追加しなくてもメモリを割り当てることができます。また、Reset
を呼び出すか、0 ではないスラック パラメータを指定して Empty
を呼び出すことで、使用中のメモリの割り当てを解除することなく要素を削除することができます。スラックを使用すると、新しいメモリを割り当てる代わりに、事前割り当てしたメモリを利用することで、マップに新しい要素を追加するプロセスを最適化できます。また、システムでメモリの割り当てを解除する必要がないため、要素の削除も容易になります。この方法は、マップを空にした直後に、同数またはそれよりも少数の要素でそのマップを再度埋める場合に、特に効果的です。
TMap
には、TArray
の Max
関数のような、事前割り当てされた要素数を確認する方法が用意されていません。
このコードでは、最大 10 個の要素を含むことができるように、Reserve
関数によって、マップの事前割り当てが行われます。
FruitMap.Reserve(10);
for (int32 i = 0; i < 10; ++i)
{
FruitMap.Add(i, FString::Printf(TEXT("Fruit%d"), i));
}
// FruitMap == [
// { Key: 9, Value: "Fruit9" },
// { Key: 8, Value: "Fruit8" },
// ...
// { Key: 1, Value: "Fruit1" },
// { Key: 0, Value: "Fruit0" }
// ]
TMap
からすべてのスラックを削除するには、Collapse
関数と Shrink
関数を使用します。Shrink
は、コンテナの最後にあるすべてのスラックを削除しますが、最初や途中にある空の要素はそのままにします。
for (int32 i = 0; i < 10; i += 2)
{
FruitMap.Remove(i);
}
// FruitMap == [
// { Key: 9, Value: "Fruit9" },
// <invalid>,
// { Key: 7, Value: "Fruit7" },
// <invalid>,
// { Key: 5, Value: "Fruit5" },
// <invalid>,
// { Key: 3, Value: "Fruit3" },
// <invalid>,
// { Key: 1, Value: "Fruit1" },
// <invalid>
// ]
FruitMap.Shrink();
// FruitMap == [
// { Key: 9, Value: "Fruit9" },
// <invalid>,
// { Key: 7, Value: "Fruit7" },
// <invalid>,
// { Key: 5, Value: "Fruit5" },
// <invalid>,
// { Key: 3, Value: "Fruit3" },
// <invalid>,
// { Key: 1, Value: "Fruit1" }
// ]
上記のコードには終わりに空の要素が 1 つあるだけなので、Shrink
は、無効な要素を 1 つ削除しただけです。すべてのスラックを削除するには、Shrink
を実行する準備のために、まず Compact
関数を呼び出して、空いたスペースをまとめます。
FruitMap.Compact();
// FruitMap == [
// { Key: 9, Value: "Fruit9" },
// { Key: 7, Value: "Fruit7" },
// { Key: 5, Value: "Fruit5" },
// { Key: 3, Value: "Fruit3" },
// { Key: 1, Value: "Fruit1" },
// <invalid>,
// <invalid>,
// <invalid>,
// <invalid>
// ]
FruitMap.Shrink();
// FruitMap == [
// { Key: 9, Value: "Fruit9" },
// { Key: 7, Value: "Fruit7" },
// { Key: 5, Value: "Fruit5" },
// { Key: 3, Value: "Fruit3" },
// { Key: 1, Value: "Fruit1" }
// ]
KeyFuncs
型に operator==
があり、非メンバー GetTypeHash
がオーバーロードする限り、何も変更せずに TMap
用のキーの型として使用できます。しかし、それらの関数をオーバーロードせずに型をキーとして使用する必要がある場合があります。そのような場合は、独自のカスタム KeyFuncs
を指定できます。キーの型に KeyFuncs
を作成するには、次に示す 2 つの typedef と 3 つの静的関数を定義する必要があります。
KeyInitType
— キーを渡すために使用する型。ElementInitType
— 要素を渡すために使用する型。KeyInitType GetSetKey(ElementInitType Element)
— 要素のキーを返します。bool Matches(KeyInitType A, KeyInitType B)
—A
とB
が等しい場合にtrue
を返します。等しくない場合はfalse
を返します。uint32 GetKeyHash(KeyInitType Key)
—Key
のハッシュ値を返します。
KeyInitType
と ElementInitType
はキーの型と要素の型の通常の渡し方の規則に対する typedef です。通常、トリビアル型の場合は値、非トリビアル型の場合は定数参照になります。要素の型が TPair
であることを思い出してください。
カスタム KeyFuncs
の例は以下のようになります。
struct FMyStruct
{
// String which identifies our key
FString UniqueID;
// Some state which doesn't affect struct identity
float SomeFloat;
explicit FMyStruct(float InFloat)
: UniqueID (FGuid::NewGuid().ToString())
, SomeFloat(InFloat)
{
}
};
template <typename ValueType>
struct TMyStructMapKeyFuncs :
BaseKeyFuncs<
TPair<FMyStruct, ValueType>,
FString
>
{
private:
typedef BaseKeyFuncs<
TPair<FMyStruct, ValueType>,
FString
> Super;
public:
typedef typename Super::ElementInitType ElementInitType;
typedef typename Super::KeyInitType KeyInitType;
static KeyInitType GetSetKey(ElementInitType Element)
{
return Element.Key.UniqueID;
}
static bool Matches(KeyInitType A, KeyInitType B)
{
return A.Compare(B, ESearchCase::CaseSensitive) == 0;
}
static uint32 GetKeyHash(KeyInitType Key)
{
return FCrc::StrCrc32(*Key);
}
};
FMyStruct
には一意の識別子と、ID に寄与しないいくつかの他のデータがあります。ここで GetTypeHash
と operator==
を使用するのは不適切です。というのも、operator==
は多目的の型のデータを無視してはならないと同時に、UniqueID
フィールドのみを調査する GetTypeHash
の動作と一致する必要があるため、無視する必要もあるからです。FMyStruct
用のカスタム KeyFuncs
を作成するには、次の手順を参考にしてください。
-
まず、
BaseKeyFuncs
を継承します。これは、KeyInitType
とElementInitType
を含むいくつかの型を定義するのに役立ちます。BaseKeyFuncs
は 2 つのテンプレート パラメータを受け取ります。マップの要素の型と、キーの型です。すべてのマップと同様に、要素の型はTPair
です。FMyStruct
をKeyType
として受け取り、テンプレート パラメータTMyStructMapKeyFuncs
をValueType
として受け取ります。置換KeyFuncs
はテンプレートです。そのため、FMyStruct
上にキーを付けたTMap
を作成するたびに新しいKeyFuncs
を定義するのではなく、マップごとにValueType
を指定することができます。2 つ目のBaseKeyFuncs
引数はキーの型です。要素のキー フィールドに格納されている、TPair
のKeyType
と混同しないようにしてください。マップは (FMyStruct
の)UniqueID
をキーとして使用する必要があるため、ここではFString
を使用しています。 -
次に、3 つの必要な
KeyFuncs
静的関数を定義します。1 つ目はGetSetKey
です。これは、所定の要素の型のキーを返します。使用する要素の型はTPair
で、キーはUniqueID
です。そのため、この関数は単純にUniqueID
を直接返します。2 つ目の静的関数は
Matches
です。この関数は (GetSetKey
で取得した) 2 つの要素のキーを受け取り、2 つが等価かどうかを比較して確認します。FString
の場合、標準的な等価テスト (operator==
) は大文字小文字を区別しません。これを大文字小文字を区別する検索に置き換えるには、Compare
関数を使用して、適切な大文字小文字の比較オプションを指定します。 -
最後の
GetKeyHash
静的関数は、抽出されたキーを受け取って、そのキーのハッシュ値を返します。Matches
関数は大文字小文字を区別するため、GetKeyHash
も大文字小文字を区別する必要があります。大文字小文字を区別するFCrc
関数は、キー文字列からハッシュ値を計算します。 -
TMap
が必要とする動作を構造体がサポートするようになったので、そのインスタンスを作成します。
KeyFuncs
パラメータが最後であり、この TMap
型がそれを必要としているからです。 TMap<
FMyStruct,
int32,
FDefaultSetAllocator,
TMyStructMapKeyFuncs<int32>
> MyMapToInt32;
// Add some elements
MyMapToInt32.Add(FMyStruct(3.14f), 5);
MyMapToInt32.Add(FMyStruct(1.23f), 2);
// MyMapToInt32 == [
// {
// Key: {
// UniqueID: "D06AABBA466CAA4EB62D2F97936274E4",
// SomeFloat: 3.14f
// },
// Value: 5
// },
// {
// Key: {
// UniqueID: "0661218447650259FD4E33AD6C9C5DCB",
// SomeFloat: 1.23f
// },
// Value: 5
// }
// ]
独自の KeyFuncs を用意するときに注意すべき点は、TMap
では、Matches
を使用して等価を比較する 2 つのアイテムが、GetKeyHash
からも同じ値を返すことを前提としていることです。さらに、これにより TMap の内部ハッシュが無効になるので、これらの関数のいずれかの結果が変更されるように既存マップ エレメントのキーを修正すると未定義の動作と見なされます。これらの規則は、デフォルトの KeyFuncs
を使用するとき、operator==
と GetKeyHash
のオーバーロードにも適用されます。
その他
CountBytes
関数と GetAllocatedSize
関数は、現在内部配列が使用しているメモリを概算します。CountBytes
は FArchive
パラメータを受け取りますが、GetAllocatedSize
は受け取りません。これらの関数は通常、統計情報の報告に使用されます。
Dump
関数は FOutputDevice
を受け取り、マップのコンテンツに関する実装情報を書き出します。この関数は通常、デバッグに使用されます。