TArray の次に Unreal Engine でよく使用されるコンテナは TMap です。 TMap は、構造がハッシュ キーに基づいているという点で TSet と似ています。 ただし、TSet とは異なり、TMap はデータをキー/値のペア (TPair<KeyType, ValueType>) として格納し、キーは格納と取得のみに使用します。
Unreal Engine のマップの種類
Unreal Engine には、次の 2 種類のマップがあります。
TMap の概要
TMap では、キー/値のペアは、各ペアが個別のオブジェクトであるかのようにマップの要素型として扱われます。 このドキュメントでは、要素はキー/値のペアを意味し、個々のコンポーネントは要素のキーまたは要素の値と呼ばれます。
要素型は
TPair<KeyType, ElementType>ですが、TPair 型を直接参照する必要はほとんどないはずです。TMap のキーは一意です。
TArray と同様に、TMap は同種のコンテナです。つまり、その要素は全て、厳密に同じ型です。
TMap は値型であり、通常のコピー、代入、デストラクター操作に加え、要素に対する強い所有権をサポートします。要素はマップが破棄されるときに破棄されます。 キーと値も値型である必要があります。
TMap はハッシュ コンテナです。つまり、キーの型は GetTypeHash 関数をサポートしており、キーの同一性を比較するための
演算子 ==を提供する必要があることを意味します
TMap および TMultimap (多くの Unreal Engine コンテナと同様) では、要素の型は自明に再配置可能であると想定しています。つまり、未加工バイトを直接コピーすることで、メモリ内のある場所から別の場所に要素を安全に移動できます。
TMultiMap の概要
複数の同一のキーの格納をサポートしています。
既存のペアと一致するキーを持つ TMap に新しいキー/値のペアを追加すると、古いペアが新しいペアに置き換わります。
TMultiMap では、コンテナに新しいペアと古いペアの両方が格納されます。
TMap は、メモリ割り当て動作をコントロールする、オプションのアロケーターを使用できます。 ただし、TArray とは異なり、これらは FHeapAllocator や TInlineAllocator などの標準の Unreal アロケーターではなく、セット アロケーターです。 セット アロケーター (TSetAllocator) は、マップで使用するハッシュ バケットの数と、ハッシュと要素の格納に使用する標準 UE アロケーターを定義します。
最終的な TMap テンプレート パラメータは KeyFuncs で、要素の型からキーを取得する方法、2 つのキーが等しいかどうかを比較する方法、キーをハッシュする方法をマップに指示します。 これらには、デフォルトでキーへの参照を返し、等価には演算子 == を使用し、ハッシュには非メンバー GetTypeHash 関数を呼び出します。 キーの型がこれらの関数をサポートしている場合、カスタム KeyFuncs を提供することなく、マップ キーとして使用できます。
TArray とは異なり、メモリ内の TMap 要素の相対的な順序は信頼性や安定したものではなく、要素に対するイテレーションでは、追加された順序と異なる順序で要素が返される可能性があります。 各要素がメモリ内で連続してレイアウト表示されることはほとんどありません。
マップの基本データ構造はスパース配列で、これは要素間のギャップを効率的にサポートする配列です。 マップから要素を取り除くと、スパース配列にギャップが現れます。 配列に新しい要素を追加すると、それらのギャップを埋めることができます。 ただし、TMap はギャップを埋めるために要素をシャッフルしないものの、マップ要素へのポインタが無効になる場合があります。これは、ストレージ全体が満杯になり、新しい要素が追加された場合に再割り当てできるためです。
マップを作成して入力する
次のコードは TMap を作成します。
TMap<int32, FString> FruitMap;TrueMap は、整数キーで識別される文字列の空の TMap になりました。 アロケーターも KeyFuncs も指定していないため、このマップは標準ヒープ割り当てを実行し、演算子 == を使用して 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 つの要素が含まれていますが、以前の Grapefruit の値はキーが「2」であったものが Pear に置き換えられています。
Add 関数は値なしでキーを受け取ることができます。 このオーバーロードされた Add が呼び出されると、値はデフォルトで構築されます。
FruitMap.Add(4);
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "" }
// ]Emplace
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 とは異なり、単一引数のコンストラクタを使用してのみ、要素をマップに配置することができます。
Append
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" },
上記の例では、結果のマップは、Add または Emplace を使用して、FruitMap2 の各要素を個別に追加した場合と等価であり、プロセスが完了すると、FruitMap2 が空になります。 つまり、TrueMap の要素とキーを共有している、FrutrutMap2 の要素は、その要素を置き換えます。
UPROPERTY マクロと editable キーワード (EditAnywhere、EditDefaultsOnly、または EditInstanceOnly) のいずれかで TMap を設定すると、エディタで要素を追加および編集できるようになります。
UPROPERTY(EditAnywhere, Category = MapsAndSets)
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
)
);
}
イテレーターは、CreateIterator 関数と CreateConstIterator 関数を使用して作成できます。
| 機能 | 説明 |
|---|---|
| 読み取り/書き込みアクセス権を持つイテレーターを返します |
| 読み取り専用のイテレーターを返します。 |
いずれの場合も、これらのイテレーターの Key 関数および Value 関数を使用して要素を調べることができます。 イテレーターを使用してサンプルの FluidMap のコンテンツを出力すると、次のようになります。
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
)
);
}値を取得
マップに特定のキーが含まれることがわかっている場合、そのキーをインデックスとして使用し、演算子 [] で対応する値を検索することができます。 非定数マップでこれを行うと、非定数の参照が返され、定数マップでは定数の参照が返されます。
演算子 [] を使用する前に、マップにそのキーが含まれているかどうかを必ず確認する必要があります。 マップにキーが含まれない場合は、アサーションが行われます。
FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
FString Val8 = FruitMap[8];
// Assert!クエリ
TMap にある要素数を判断するには、Num 関数を呼び出します。
int32 Count = FruitMap.Num();
// Count == 6マップに特定のキーが含まれているかどうかを判断するには、Contains 関数を呼び出します。
bool bHas7 = FruitMap.Contains(7);
bool bHas8 = FruitMap.Contains(8);
// bHas7 == true
// bHas8 == falseマップにキーが含まれているかどうかが不明な場合は、Contains 関数を使用して確認し、演算子 [] を使用できます。 ただし、これは最適ではありません。取得が成功するには、同じキーに対して 2 つのルックアップが必要だからです。
Find 関数は、これらの動作を単一のルックアップにまとめます。 Find は、マップにキーが含まれている場合は、その要素の値へのポインタを返し、含まれていない場合は null ポインタを返します。 定数マップで Find を呼び出すと定数ポインタが返されます。
FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
// Ptr8 == nullptrまたは、確実にクエリから有効な結果が返されるように、FindOrAdd または 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" }
// ]
FindOrAdd は新しいエントリをマップに追加できるため、この例で Ref8 を初期化する際には、以前に取得したポインタや参照が無効になる可能性があります。 これは、新しい要素を入れるためにマップのバックエンド ストレージを拡張する必要がある場合に、メモリを割り当て、既存のデータを移動させる追加操作の結果です。 上の例では、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 (削除)
Remove 関数を使用し、除去する要素のキーを指定すると、マップから要素を除去できます。 戻り値は削除された要素の数です。キーに一致する要素がマップに含まれなかった場合はゼロになる場合があります
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」の部分は、キーが存在しない場合にマップがチェックを呼び出すことを示します。
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" }
// ]
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" }
// ]
最後に、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(); // You can also use Reset() here.
// FruitMapCopy == []Empty の場合、マップにどの程度のスラックを残すかを示すパラメータを受け取ることができ、Reset の場合は常にできるだけ多くのスラックを残します。
Sort (ソート)
TMap をキーまたは値でソートすることができます。 ソートした後、マップのイテレーションではソート順に要素が表示されます。ただし、この動作は次にマップを変更するまで保証されます。 ソートは不安定であるため、TMultiMap 内の同等の要素は任意の順序で表示されることがあります。
それぞれ 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" }
// ]
演算子
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 == [
TMap は MoveTemp 関数を使用して呼び出すことができるムーブ セマンティクスをサポートします。 移動後、ソース マップは必ず空になります。
FruitMap = MoveTemp(NewMap);
// FruitMap == [
// { Key: 4, Value: "Kiwi" },
// { Key: 5, Value: "Apple" },
// { Key: 9, Value: "Melon" }
// ]
// NewMap == []スラック
スラックは要素を含まない、割り当てられたメモリです。 Reserve を呼び出すことで要素を追加することなくメモリを割り当てることができます。また、Reset を呼び出すか、非ゼロのスラック パラメータで Empty を呼び出すことで使用されていたメモリの割り当てを解除することなく要素を削除できます。 スラックは、新しいメモリを割り当てる代わりに事前割り当てメモリを使用することにより、マップに新しい要素を追加するプロセスを最適化します。 また、システムではメモリの割り当てを解除する必要がないため、要素の削除にも役立ちます。 これは、同数以下の要素をすぐに再投入することが想定されているマップを空にする場合に特に効率的です。
TMap には、TArray の Max 関数のように事前割り当てされている要素の数を確認する方法はありません。
以下のコードでは、Reserve 関数により、最大 10 個の要素を含むマップ用のスペースが割り当てられます。
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" },
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" },
上記のコードでは末尾に空の要素が 1 つしかないため、Shrink によって削除された要素は 1 つだけです。 全てのスラックを除去するには、まず Compact 関数を使用して、Shrink の空のスペースをグループ化します。
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>,
KeyFuncs
型に演算子 == と非メンバー GetTypeHash オーバーロードがある限り、変更せずに TMap のキーの型として使用できます。 ただし、それらの関数をオーバーロードすることなく、型をキーとして使用する必要がある場合があります。 そのような場合は、独自のカスタム KeyFunc を提供することができます。 キー型の KeyFunc を作成するには、次のように、2 つの typedef と 3 つの静的な機能を定義する必要があります。
| 型を定義 | 説明 |
|---|---|
| キーを渡すために使用する型。 |
| 要素を渡すために使用される型。 |
| 機能 | 説明 |
|---|---|
| 要素のキーを返します。 |
|
|
|
|
KeyInitType と ElementInitType は、キーの型と要素の型の通常の引き渡し規則に対する typedef です。 これらは通常、トリビアル型に対しては値となり、非トリビアル型には定数参照となります。 マップの要素タイプは TPair であることを思い出してください。
次のコード スニペットはカスタム KeyFuncs の一例です。
MyCustomKeyFuncs.cpp
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())
FMyStruct は、一意の識別子と、その識別子に影響しないその他のデータを備えています。 GetTypeHashと演算子 == の組み合わせは、ここでは不適切です。演算子 == は、汎用的な使用方法の場合、型のデータを無視すべきではありませんが、同時に、配列内の GetTypeHash の動作と一貫性を持たせるために無視する必要があるためです。これは UniqueID フィールドのみを確認します。
FMyStruct のカスタム KeyFuncs を作成するには、以下の手順に従います。
KeyInitTypeやElementInitTypeなど、いくつかの便利な型を定義するため、BaseKeyFuncsから継承します。BaseKeyFuncsは次の 2 つのテンプレート パラメータを取ります。マップの要素型。
全てのマップと同様に、要素型は
TPairで、FMyStructをそのKeyTypeとして、TMyStructMapKeyFuncsのテンプレート パラメータをValueTypeとして受け取ります。 置換KeyFuncsはテンプレートであるため、マップごとにValueTypeを指定できます。FMyStructでキー設定された TMap を作成するたびに新しいKeyFuncsを定義する必要はありません。
キーの型。
2 つ目の
BaseKeyFuncs引数はキーの型です。TPair のKeyType(要素ストアの Key フィールド) と混同しないようにしてください。 このマップではUniqueID(FMyStructから) をキーとして使用する必要があるため、ここではFStringが使用されます
必要な 3 つの
KeyFuncs静的関数を定義します。1 つ目は GetSetKey で、指定された要素型のキーを返します。 要素の型は
TPairで、キーはUniqueIDであるため、この関数は単にUniqueIDを直接返すことができます。2 つ目の静的関数は Matches で、
GetSetKeyによって取得した 2 つの要素のキーを取得し、それらが等しいかどうかを比較します。FStringでの場合、標準の等価テスト (演算子 ==) では大文字と小文字が区別されません。大文字と小文字を区別する検索に置き換えるため、適切な大文字と小文字比較オプションを指定してCompare()関数を使用します。3 つ目の静的関数は
GetKeyHashで、これは抽出したキーを取得し、そのハッシュ値を返します。Matches関数は大文字と小文字を区別するため、GetKeyHashも大文字と小文字を区別する必要があります。 大文字と小文字が区別される FCrc 関数が、キー文字列からハッシュ値を計算します。
構造体が TMap に必要な動作をサポートするようになったため、TMap のインスタンスを作成できます。
C++TMap< FMyStruct, int32, FDefaultSetAllocator, TMyStructMapKeyFuncs<int32> > MyMapToInt32; // Add some elements MyMapToInt32.Add(FMyStruct(3.14f), 5); MyMapToInt32.Add(FMyStruct(1.23f), 2);この例では、デフォルトのセット アロケーターが指定されています。 これは、
KeyFuncsパラメータが最後であり、このTMap型ではそれが必要であるためです。
独自の KeyFunc を提供する場合、TMap は、Matches と等しいものとして比較される 2 つのアイテムも GetKeyHash から同じ値を返すと想定していることに注意してください。 さらに、これらの関数のいずれかの結果を変更するように既存のマップ要素のキーを変更することは、マップの内部ハッシュが無効になるため、未定義の動作と見なされます。 これらのルールは、デフォルトの KeyFunc を使用するときに、演算子 == および GetKeyHash のオーバーロードにも適用されます。
その他
CountBytes および GetAllocatedSize 関数は、内部配列が現在使用しているメモリ量を推定します。 CountBytes は FArchive パラメータを受け取りますが、GetAllocatedSize は受け取りません。 これらの関数は通常、統計情報のレポートに使用されます。
Dump 関数は FOutputDevice を受け取り、マップのコンテンツに関する実装情報を書き出します。 この関数は通常、デバッグに使用されます。