Nach TArray ist TMap der am häufigsten verwendete Container in der Unreal Engine. TMap ähnelt TSet darin, dass es auf Hashing-Schlüsseln basiert. Allerdings speichert TMap im Gegensatz zu TSet Daten als Schlüssel-Wert-Paare (TPair<KeyType, ValueType>) und verwendet Schlüssel nur zum Speichern und Abrufen.
Arten von Maps in der Unreal Engine
In der Unreal Engine gibt es zwei Arten von Maps:
Übersicht über TMap
In einer TMap werden Schlüssel-Wert-Paare als Elementtyp der Map behandelt, als wäre jedes Paar ein individuelles Objekt. In diesem Dokument bedeutet Element ein Schlüssel-Wert-Paar, während einzelne Komponenten als Schlüssel des Elements oder Wert des Elements bezeichnet werden.
Der Elementtyp ist ein
TPair<KeyType, ElementType>, allerdings sollte es selten vorkommen, dass Sie direkt auf den TPair-Typ verweisen müssen.TMap-Schlüssel sind einzigartig.
Ähnlich wie TArray ist TMap ein homogener Container, was bedeutet, dass alle Elemente genau den gleichen Typ haben.
TMap ist ein Werttyp und unterstützt die üblichen Kopier-, Zuweisungs- und Destructor-Operationen sowie den starken Besitz seiner Elemente, die zerstört werden, wenn die Map zerstört wird. Der Schlüssel und der Wert müssen ebenfalls Werttypen sein.
TMap ist ein Hashing-Container, was bedeutet, dass der Schlüsseltyp die GetTypeHash-Funktion unterstützen und einen
operator==zum Vergleich von Schlüsseln auf Gleichheit bieten muss.
TMap und TMultimap (wie viele Unreal-Engine-Container) gehen davon aus, dass der Elementtyp trivial verschiebbar ist, was bedeutet, dass Elemente sicher von einem Ort im Speicher an einen anderen verschoben werden können, indem die Roh-Bytes direkt kopiert werden.
Übersicht über TMultiMap
Unterstützt das Speichern mehrerer, identischer Schlüssel.
Wenn Sie ein neues Schlüssel-Wert-Paar zu einer TMap hinzufügen, dessen Schlüssel mit einem bestehenden Paar übereinstimmt, ersetzt das neue Paar das alte.
In einer TMultiMap speichert der Container sowohl das neue als auch das alte Paar.
TMap kann einen optionalen Allokator verwenden, um das Speicherallokationsverhalten zu steuern. Im Gegensatz zu TArray handelt es sich dabei jedoch um Set-Allokatoren und nicht um die Standard-Unreal-Allokatoren wie FHeapAllocator und TInlineAllocator. Set-Allokatoren (TSetAllocator) definieren, wie viele Hash-Buckets die Map verwenden soll und welche Standard-UE-Allokatoren für die Hash- und Elementspeicherung verwendet werden sollen.
Der letzte Parameter der TMap-Vorlage ist KeyFuncs, der der Map mitteilt, wie sie den Schlüssel vom Elementtyp abruft, wie zwei Schlüssel auf Gleichheit verglichen werden und wie der Schlüssel gehasht wird. Diese haben Standardwerte, die eine Referenz zum Schlüssel zurückgeben, dann operator== für Gleichheit verwenden und die Nicht-Mitglieds-Funktion GetTypeHash für das Hashing aufrufen. Wenn Ihr Schlüsseltyp diese Funktionen unterstützt, können Sie ihn als Map-Schlüssel verwenden, ohne einen benutzerdefinierten KeyFuncs anzugeben.
Im Gegensatz zu TArray ist die relative Reihenfolge von TMap-Elementen im Speicher nicht zuverlässig oder stabil, und das Iterieren über die Elemente gibt diese wahrscheinlich in einer anderen Reihenfolge zurück als der Reihenfolge, in der sie hinzugefügt wurden. Elemente werden wahrscheinlich nicht zusammenhängend im Speicher abgelegt.
Die Basisdatenstruktur einer Map ist ein dünnbesetztes Array, also ein Array, das Lücken zwischen seinen Elementen effizient unterstützt. Wenn Elemente aus der Map entfernt werden, erscheinen Lücken im dünnbesetzten Array. Durch Hinzufügen neuer Elemente zum Array können diese Lücken geschlossen werden. Auch wenn TMap Elemente nicht mischt, um Lücken zu füllen, können Pointer auf Map-Elemente trotzdem ungültig werden, da der gesamte Speicherplatz neu zugewiesen werden kann, wenn er voll ist und neue Elemente hinzugefügt werden.
Map erstellen und füllen
Der folgende Code erstellt eine TMap:
TMap<int32, FString> FruitMap;FruitMap ist nun eine leere TMap aus Strings, die durch Ganzzahl-Schlüssel identifiziert werden. Wir haben weder einen Allokator noch KeyFuncs spezifiziert, also führt die Map eine Standard-Heap-Allokation durch und vergleicht den Schlüssel des Typs int32 mit operator== und hasht den Schlüssel mit GetTypeHash. Zu diesem Zeitpunkt wurde noch kein Speicher zugewiesen.
Hinzufügen
Die Standardmethode zum Befüllen einer Map besteht darin, Add mit einem Schlüssel und einem Wert aufzurufen:
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" }
// ]Obwohl die Elemente hier in der Reihenfolge aufgeführt sind, in der sie eingefügt wurden, gibt es keine Garantie für ihre tatsächliche Reihenfolge im Speicher. Bei einer neuen Map befinden sie sich wahrscheinlich in der Reihenfolge des Einfügens, aber je mehr Elemente eingefügt und entfernt werden, desto unwahrscheinlicher wird es, dass neue Elemente am Ende erscheinen.
Dies ist keine TMultiMap; daher sind die Schlüssel garantiert einzigartig. Das Folgende ist das Ergebnis des Versuchs, einen duplizierten Schlüssel hinzuzufügen:
FruitMap.Add(2, TEXT("Pear"));
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" }
// ]Die Map enthält noch immer drei Elemente, jedoch wurde der vorherige Wert „Grapefruit“ mit dem Schlüssel 2 durch „Pear“ ersetzt.
Die Add-Funktion kann einen Schlüssel ohne Wert akzeptieren. Wenn dieses überladene Add aufgerufen wird, wird der Wert per Standard-Constructor erstellt:
FruitMap.Add(4);
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "" }
// ]Emplace
Wie bei TArray können wir Emplace anstatt Add verwenden, um die Erstellung von Provisorien beim Einfügen in die Map zu vermeiden:
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" }
// ]Hier werden der Schlüssel und der Wert direkt an ihre jeweiligen Typ-Constructoren weitergegeben. Während dies für den int32-Key nicht aussagekräftig ist, vermeidet es die Erstellung eines temporären FString für den Wert. Im Gegensatz zu TArray können Elemente nur mit Ein-Argument-Constructoren in einer Map platziert werden.
Anhängen
Sie können zwei Maps mit der Append-Funktion zusammenführen, die alle Elemente von der Argument-Map in die aufrufende Objekt-Map verschiebt:
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" },
Die im obigen Beispiel erzeugte Map entspricht dem Einsatz von Add oder Emplace, um jedes Element der FruitMap2 einzeln hinzuzufügen und FruitMap2 nach Abschluss des Vorgangs zu leeren. Das bedeutet, dass jedes Element aus FruitMap2, das seinen Schlüssel mit einem Element teilt, das bereits in FruitMap vorliegt, dieses Element ersetzt.
Wenn Sie die TMap mit dem UPROPERTY-Makro und einem der „bearbeitbaren“ Stichworte (EditAnywhere, EditDefaultsOnly oder EditInstanceOnly) markieren, können Sie Elemente im Editor hinzufügen und bearbeiten.
UPROPERTY(EditAnywhere, Category = MapsAndSets)
TMap<int32, FString> FruitMap;Iterieren
Die Iteration über TMaps ist ähnlich wie bei TArrays. Sie können die C++-ranged-for Funktion verwenden. Beachten Sie, dass der Elementtyp ein TPair ist:
for (auto& Elem : FruitMap)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
Elem.Key,
*Elem.Value
)
);
}
Sie können Iteratoren mit den Funktionen CreateIterator und CreateConstIterator erstellen.
| Funktion | Beschreibung |
|---|---|
| Gibt einen Iterator mit Lese-/Schreibzugriff zurück. |
| Gibt einen schreibgeschützten Iterator zurück. |
In jedem Fall kannst du die Key- und Value-Funktionen dieser Iteratoren verwenden, um die Elemente zu untersuchen. Die Ausgabe der Inhalte unseres Beispiels FruitMap mit Iteratoren sähe so aus:
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
)
);
}Wert abrufen
Wenn Sie wissen, dass Ihre Map einen bestimmten Schlüssel enthält, können Sie den entsprechenden Wert mit dem operator[] nachschlagen, wobei der Schlüssel als Index verwendet wird. Wenn Sie das mit einer Nicht-const-Map tun, wird eine Nicht-const-Referenz zurückgegeben, während eine const-Map eine const-Referenz zurückgibt.
Du solltest immer prüfen, ob die Map den Schlüssel enthält, bevor du operator[] verwendest. Enthält die Map den Schlüssel nicht, wird assert durchgeführt.
FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
FString Val8 = FruitMap[8];
// Assert!Abfrage
Um zu bestimmen, wie viele Elemente sich aktuell in einer TMap befinden, rufen Sie die Num-Funktion auf:
int32 Count = FruitMap.Num();
// Count == 6Um zu bestimmen, ob eine Map einen bestimmten Schlüssel enthält oder nicht, rufen Sie die Contains-Funktion auf:
bool bHas7 = FruitMap.Contains(7);
bool bHas8 = FruitMap.Contains(8);
// bHas7 == true
// bHas8 == falseWenn Sie sich nicht sicher sind, ob Ihre Map einen Schlüssel enthält oder nicht, können Sie dies mit der Contains-Funktion prüfen und dann operator[] verwenden. Dies ist jedoch nicht optimal, da ein erfolgreicher Aufruf zwei Lookup-Vorgänge für denselben Schlüssel umfasst.
Die Find-Funktion kombiniert dieses Verhalten mit einem einzigen Lookup. Find gibt einen Pointer auf den Wert des Elements zurück, wenn die Map den Schlüssel enthält, oder einen Null-Pointer, wenn dieser nicht vorhanden ist. Der Aufruf von Find auf einer const-Map gibt einen const-Pointer zurück.
FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
// Ptr8 == nullptrAlternativ können Sie FindOrAdd oder FindRef verwenden, um sicherzustellen, dass Sie ein gültiges Ergebnis für Ihre Abfrage erhalten.
| Funktion | Beschreibung |
|---|---|
| Gibt eine Referenz auf den Wert zurück, der mit dem von Ihnen bereitgestellten Schlüssel assoziiert ist. Befindet sich der Schlüssel nicht in der Map, gibt
|
| Gibt trotz seines Namens eine Kopie des mit deinem Schlüssel assoziierten Werts zurück, oder einen standardmäßigen Wert, sollte Ihr Schlüssel nicht in der Map gefunden werden. |
Da FindOrAdd und FindRef auch dann erfolgreich sind, wenn der Schlüssel nicht in der Map gefunden wurde, können Sie sie bedenkenlos aufrufen, ohne die üblichen Sicherheitsprozeduren wie die vorherige Überprüfung von Includes oder die Nullprüfung des Ergebniswerts durchführen zu müssen.
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" }
// ]
Da FindOrAdd neue Einträge zur Map hinzufügen kann, wie es bei der Initialisierung von Ref8 in unserem Beispiel der Fall ist, könnten zuvor erhaltene Pointer oder Referenzen ungültig werden. Das ist ein Ergebnis der Additionsoperation, die Speicher allokiert und bestehende Daten verschiebt, wenn der Speicherplatz der Karte vergrößert werden muss, um das neue Element zu enthalten. Im obigen Beispiel wird Ref7 möglicherweise nach Ref8 nach dem Aufrufen von FindOrAdd(8) ungültig.
Die FindKey-Funktion führt einen umgekehrten Lookup durch, sodass ein angegebener Wert mit einem Schlüssel abgeglichen wird, und gibt einen Pointer auf den ersten Schlüssel zurück, der mit dem angegebenen Wert gekoppelt ist. Die Suche nach einem Wert, der nicht in der Map vorhanden ist, gibt einen Null-Pointer zurück.
const int32* KeyMangoPtr = FruitMap.FindKey(TEXT("Mango"));
const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat"));
// *KeyMangoPtr == 5
// KeyKumquatPtr == nullptrLookups nach Wert sind langsamer (lineare Zeit) als Lookups nach Schlüssel. Der Grund dafür ist, dass die Map nach Schlüssel und nicht nach Wert gehasht wird. Hat eine Map außerdem mehrere Schlüssel mit demselben Wert, kann FindKey einen beliebigen von ihnen zurückgeben.
Die Funktionen GenerateKeyArray und GenerateValueArray bevölkern ein TArray mit einer Kopie aller Schlüssel bzw. Werte. In beiden Fällen wird das weitergegebene Array vor dem Bevölkern geleert, sodass die resultierende Anzahl der Elemente immer der Anzahl der Elemente in der Map entspricht.
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","" ]Entfernen
Sie können Elemente mit der Remove-Funktion aus einer Map entfernen und den Schlüssel des zu entfernenden Elements angeben. Der Ergebniswert ist die Anzahl der Elemente, die entfernt wurden, und kann 0 sein, wenn die Map keine Elemente enthielt, die dem Schlüssel entsprechen.
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" }
// ]Das Entfernen von Elementen kann Lücken in der Datenstruktur hinterlassen, die Sie bei der Visualisierung der Map im Überwachungsfenster von Visual Studio sehen können, aber diese wurden aus Gründen der Klarheit weggelassen.
Mit der Funktion FindAndRemoveChecked können Sie ein Element von der Map entfernen und seinen Wert zurückgeben. Der „checked“-Teil des Namens gibt an, dass die Map check aufruft, wenn der Schlüssel nicht existiert.
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" }
// ]
Die RemoveAndCopyValue-Funktion ähnelt der Remove-Funktion, aber kopiert den Wert des entfernten Elements in einen Referenzparameter. Ist der angegebene Schlüssel nicht in der Map vorhanden, bleibt der Output-Parameter unverändert und die Funktion gibt False zurück.
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" }
// ]
Zum Schluss können Sie mit den Funktionen Empty oder Reset alle Elemente von der Map entfernen.
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 kann einen Parameter akzeptieren, der angibt, wie viel Slack in der Map gelassen werden soll, während Reset immer so viel Slack wie möglich lässt.
Sortieren
Sie können eine TMap nach Schlüssel oder Wert sortieren. Nach der Sortierung werden bei der Iteration über die Map die Elemente in sortierter Reihenfolge dargestellt. Dieses Verhalten ist aber nur bis zur nächsten Modifikation der Map garantiert. Die Sortierung ist instabil, daher können gleichartige Elemente in einer TMultiMap in beliebiger Reihenfolge erscheinen.
Sie können die Sortierung nach Schlüssel oder Wert mit der Funktion KeySort oder ValueSort durchführen. Beide Funktionen akzeptieren ein binäres Prädikat, das die Sortierungsreihenfolge angibt.
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" }
// ]
Operatoren
Wie TArray ist TMap ein regulärer Werttyp und kann mit dem Standard-Constructor oder dem Zuweisungsoperator kopiert werden. Maps besitzen ihre Elemente vollständig, also ist das Kopieren einer Map vollständig; die neue Map hat ihre eigene Kopie der Elemente.
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 unterstützt die Bewegungssemantik, die mit der MoveTemp-Funktion aufgerufen werden kann. Nach einem Verschieben ist die Quell-Map garantiert leer:
FruitMap = MoveTemp(NewMap);
// FruitMap == [
// { Key: 4, Value: "Kiwi" },
// { Key: 5, Value: "Apple" },
// { Key: 9, Value: "Melon" }
// ]
// NewMap == []Slack
Slack ist zugewiesener Speicher, der kein Element enthält. Sie können Speicher allokieren, ohne Elemente hinzuzufügen, indem Sie Reserve aufrufen. Sie können zudem Elemente entfernen, ohne den von ihnen belegten Speicher freizugeben, indem Sie Reset oder Empty mit einem Slack-Parameter ungleich Null aufrufen. Slack optimiert den Prozess des Hinzufügens neuer Elemente zur Map, indem es vorab zugewiesenen Speicher verwendet, anstatt neuen Speicher allokieren zu müssen. Dies kann außerdem bei der Entfernung von Elementen helfen, da das System keinen Speicher freigeben muss. Das ist besonders effizient, wenn Sie eine Map leeren, die sofort mit der gleichen Anzahl von Elementen oder weniger neu gefüllt werden soll.
TMap bietet im Gegensatz zur Max-Funktion in TArray keine Möglichkeit, zu überprüfen, wie viele Elemente vorab zugewiesen wurden.
Im folgenden Code weist die Funktion Reserve Raum für die Map zu, der bis zu zehn Elemente enthalten kann:
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" },
Um sämtlichen Slack aus einer TMap zu entfernen, verwenden Sie die Funktionen Collapse und Shrink. Shrink entfernt sämtlichen Slack vom Ende des Containers, aber belässt alle leeren Elemente in der Mitte oder am Anfang.
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" },
DurchSchrumpfen wurde nur ein ungültiges Element im obigen Code entfernt, weil am Ende nur ein leeres Element war. Um sämtlichen Slack zu entfernen, verwenden Sie zuerst die Compact-Funktion, damit die leeren Bereiche für Shrink gruppiert werden.
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
Solange ein Typ einen operator==- und eine Nicht-Mitglieds-GetTypeHash--Überladung hat, können Sie ihn ohne Änderungen als Schlüsseltyp für eine TMap verwenden. Sie möchten vielleicht aber Typen als Schlüssel verwenden, ohne diese Funktionen zu überladen. In diesen Fällen können Sie Ihre eigenen benutzerdefinierte KeyFuncs bereitstellen. Um KeyFuncs für Ihren Schlüsseltyp zu erstellen, müssen Sie zwei Typedefs und drei statische Funktionen wie folgt definieren:
| Typdefinition | Beschreibung |
|---|---|
| Typ, der verwendet wird, um Schlüssel zu übergeben. |
| Typ, mit dem Elemente übergeben werden. |
| Funktion | Beschreibung |
|---|---|
| Gibt den Schlüssel eines Elements zurück. |
| Gibt |
| Gibt den Hash-Wert von |
KeyInitType und ElementInitType sind Typedefs der normalen Übergabekonvention des Schlüsseltyps und Elementtyps. In der Regel ist das ein Wert für triviale Typen und eine const-Referenz für nicht-triviale Typen. Zur Erinnerung: Der Elementtyp einer Map ist ein TPair.
Der folgende Code-Ausschnitt ist ein Beispiel für ein benutzerdefiniertes 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 verfügt über eine einzigartige Kennung sowie über einige andere Daten, die nicht zu seiner Identität beitragen. GetTypeHash und operator== wären hier unpassend, weil operator== keine der Typdaten für allgemeine Zwecke ignorieren sollte, aber gleichzeitig müsste, um mit dem Verhalten von GetTypeHash konsistent zu sein, das nur das UniqueID-Feld betrachtet.
Befolge diese Schritte, um ein benutzerdefiniertes KeyFuncs für FMyStruct zu erstellen:
Wird von
BaseKeyFuncsübernommen, da es einige hilfreiche Typen definiert, darunterKeyInitTypeundElementInitType.BaseKeyFuncsbenötigt zwei Vorlagenparameter:Den Elementtyp der Map.
Wie bei allen Maps ist der Elementtyp ein
TPair, derFMyStructalsKeyTypeund den Parameter vonTMyStructMapKeyFuncsalsValueTypeverwendet. Die Ersatz-KeyFuncsist eine Vorlage, damit SieValueTypefür jede einzelne Map spezifizieren können, anstatt jedes Mal ein neuesKeyFuncsdefinieren zu müssen, wenn Sie eine TMap erstellen möchten, die mitFMyStructverknüpft ist.
Den Typ unseres Schlüssels.
Das zweite
BaseKeyFuncs-Argument ist der Typ des Schlüssels, nicht zu verwechseln mit demKeyTypeaus TPair, dem Key-Feld des Elementspeichers. Da diese Map dieUniqueID(ausFMyStruct) als Schlüssel verwenden sollte, wird hierFStringeingesetzt.
Definieren Sie die drei erforderlichen statischen
KeyFuncs-Funktionen.Die erste ist GetSetKey, welche den Schlüssel für ein bestimmtes Element zurückgibt. Unser Elementtyp ist
TPairund unser Schlüssel istUniqueID, also kann die Funktion auch einfachUniqueIDdirekt zurückgeben.Die zweite statische Funktion ist Matches. Sie nimmt die Schlüssel zweier Elemente entgegen, die von
GetSetKeyabgerufen wurden, und vergleicht sie, um herauszufinden, ob sie äquivalent sind. FürFStringbeachtet der Standard-Äquivalenztest (operator==) die Groß-/Kleinschreibung nicht. Um dies durch eine Suche mit Berücksichtigung der Groß-/Kleinschreibung zu ersetzen, verwenden Sie dieCompare()-Funktion mit der entsprechenden Option für den Groß-/Kleinschreibung-Vergleich.Die dritte statische Funktion ist
GetKeyHash. Sie nimmt einen extrahierten Schlüssel entgegen und gibt einen Hash-Wert dafür zurück. Da dieMatches-Funktion Groß- und Kleinschreibung beachtet, muss dies auch beiGetKeyHashder Fall sein. Eine FCrc-Funktion, die Groß- und Kleinschreibung beachtet, berechnet den Hash-Wert aus dem Schlüssel-String.
Da die Struktur jetzt die Verhaltensweisen unterstützt, die für TMap erforderlich sind, können Sie Instanzen davon erstellen.
C++TMap< FMyStruct, int32, FDefaultSetAllocator, TMyStructMapKeyFuncs<int32> > MyMapToInt32; // Add some elements MyMapToInt32.Add(FMyStruct(3.14f), 5); MyMapToInt32.Add(FMyStruct(1.23f), 2);In diesem Beispiel wird der standardmäßig eingestellte Allokator angegeben. Der Grund dafür ist, dass der
KeyFuncs-Parameter der letzte ist und dieserTMap-Typ ihn erfordert.
Bei der Bereitstellung Ihres eigenen KeyFuncs sollten Sie sich bewusst sein, dass TMap annimmt, dass zwei Elemente, die als gleich mit Matches verglichen werden, auch denselben Wert von GetKeyHash zurückgeben. Darüber hinaus wird jede Modifikation des Schlüssels eines bestehenden Map-Elements, die die Ergebnisse einer dieser Funktionen verändert, als undefiniertes Verhalten betrachtet, da dadurch der interne Hash der Map ungültig wird. Diese Regeln gelten auch für Überladungen von operator== und GetKeyHash, wenn das standardmäßige KeyFuncs verwendet wird.
Verschiedenes
Die Funktionen CountBytes und GetAllocatedSize schätzen, wie viel Speicher das interne Array aktuell verwendet. CountBytes akzeptiert einen FArchive-Parameter, GetAllocatedSize jedoch nicht. Diese Funktionen werden in der Regel für die Berichterstattung zu Statistiken verwendet.
Die Dump-Funktion nimmt ein FOutputDevice und schreibt einige Implementierungsinformationen über die Inhalte der Map aus. Diese Funktion wird normalerweise für das Debugging verwendet.