TMap

TMap は、主に 2 つの型 (キーの型と値の型) によって定義されます。これらは、関連付けられたペアとしてマップ内に格納されます。

TArray の後、Unreal Engine で最もよく使用されているコンテナが TMap です。TMap は、ハッシュ キーに基づく構造体である点で TSet に似ています。しかし、TSet とは異なり、このコンテナはデータをキーと値のペア (TPair<KeyType, ValueType>) として格納します。キーは格納と取得のためだけに使用されます。

マップには 2 タイプあります。TMapTMultiMap です。これら 2 つの違いは、TMap のキーは一意であるのに対し、TMultiMap は複数の同一のキーの格納をサポートしていることです。既存のペアと一致するキーを使用して TMap に新たにキーと値のペアを追加するとき、新しいペアによって古いペアが置き換えられます。TMultiMap では、コンテナに新しいペアと古いペアの両方が格納されます。

TMap

TMap では、キーと値のペアが個別のオブジェクトであるかのように、各ペアがマップの要素の型として扱われます。このドキュメントでは、「要素」とはキーと値のペアを指し、個々のコンポーネントは、「要素のキー」または「要素の値」と呼びます。要素の型は実際は TPair<KeyType, ElementType> ですが、TPair 型を直接言及する必要があることはほとんどありません。

TArray と同様に、TMap は均一なコンテナです。つまり、要素はすべて、厳密に同じ型です。TMap は値型でもあり、通常のコピー、代入、デストラクタの操作をサポートしています。また、要素の強力な所有権をサポートしているため、マップが破棄されたら値も破棄されます。キーと値も値型です。

TMap はハッシュ コンテナであり、キーの型は GetTypeHash をサポートし、キーを比較して同一かどうかを確認するための operator== を提供する必要があります。ハッシュについては、後で詳しく説明します。

TMap はオプションでアロケータを受け取って、メモリ割り当て動作を制御することもできます。しかし、TArray とは異なり、FHeapAllocatorTInlineAllocator などの標準の 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 の要素と同一の要素は置き換えられます。

TMapUPROPERTY マクロと「編集可能」なキーワード (EditAnywhereEditDefaultsOnly、または 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 は新しい要素を作成しないので、定数マップと非定数マップの両方で利用できます。FindOrAddFindRef はキーがマップ内にない場合も正常に実行できるため、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 == []

EmptyReset は似ていますが、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 には、TArrayMax 関数のような、事前割り当てされた要素数を確認する方法が用意されていません。

このコードでは、最大 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)AB が等しい場合に true を返します。等しくない場合は false を返します。

  • uint32 GetKeyHash(KeyInitType Key)Key のハッシュ値を返します。

KeyInitTypeElementInitType はキーの型と要素の型の通常の渡し方の規則に対する 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 に寄与しないいくつかの他のデータがあります。ここで GetTypeHashoperator== を使用するのは不適切です。というのも、operator== は多目的の型のデータを無視してはならないと同時に、UniqueID フィールドのみを調査する GetTypeHash の動作と一致する必要があるため、無視する必要もあるからです。FMyStruct 用のカスタム KeyFuncs を作成するには、次の手順を参考にしてください。

  1. まず、BaseKeyFuncs を継承します。これは、KeyInitTypeElementInitType を含むいくつかの型を定義するのに役立ちます。

    BaseKeyFuncs は 2 つのテンプレート パラメータを受け取ります。マップの要素の型と、キーの型です。すべてのマップと同様に、要素の型は TPair です。FMyStructKeyType として受け取り、テンプレート パラメータ TMyStructMapKeyFuncsValueType として受け取ります。置換 KeyFuncs はテンプレートです。そのため、FMyStruct 上にキーを付けた TMap を作成するたびに新しい KeyFuncs を定義するのではなく、マップごとに ValueType を指定することができます。2 つ目の BaseKeyFuncs 引数はキーの型です。要素のキー フィールドに格納されている、TPairKeyType と混同しないようにしてください。マップは (FMyStruct の) UniqueID をキーとして使用する必要があるため、ここでは FString を使用しています。

  2. 次に、3 つの必要な KeyFuncs 静的関数を定義します。1 つ目は GetSetKey です。これは、所定の要素の型のキーを返します。使用する要素の型は TPair で、キーは UniqueID です。そのため、この関数は単純に UniqueID を直接返します。

    2 つ目の静的関数は Matches です。この関数は (GetSetKey で取得した) 2 つの要素のキーを受け取り、2 つが等価かどうかを比較して確認します。FString の場合、標準的な等価テスト (operator==) は大文字小文字を区別しません。これを大文字小文字を区別する検索に置き換えるには、Compare 関数を使用して、適切な大文字小文字の比較オプションを指定します。

  3. 最後の GetKeyHash 静的関数は、抽出されたキーを受け取って、そのキーのハッシュ値を返します。Matches 関数は大文字小文字を区別するため、GetKeyHash も大文字小文字を区別する必要があります。大文字小文字を区別する FCrc 関数は、キー文字列からハッシュ値を計算します。

  4. 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 関数は、現在内部配列が使用しているメモリを概算します。CountBytesFArchive パラメータを受け取りますが、GetAllocatedSize は受け取りません。これらの関数は通常、統計情報の報告に使用されます。

Dump 関数は FOutputDevice を受け取り、マップのコンテンツに関する実装情報を書き出します。この関数は通常、デバッグに使用されます。

タグ
Unreal Engine のドキュメントを改善するために協力をお願いします!どのような改善を望んでいるかご意見をお聞かせください。
調査に参加する
キャンセル