언리얼 엔진에서 TArray 다음으로 가장 자주 사용되는 컨테이너는 TMap입니다. TMap이 TSet와 비슷한 점은 그 구조가 키 해시 기반이라는 점입니다. 그러나 TSet와 달리 TMap은 데이터를 키-값 쌍(TPair<KeyType, ValueType>)으로 저장하며, 키는 저장 및 검색 용도로만 사용합니다.
언리얼 엔진의 맵 타입
언리얼 엔진에는 두 가지 타입의 맵이 있습니다.
TMap 개요
TMap에서 키-값 쌍은 각 쌍이 개별 오브젝트인 것처럼 맵의 요소 타입으로 취급됩니다. 이 문서에서 요소는 키-값 쌍을 의미하며, 개별 컴포넌트는 요소의 키 또는 요소의 값으로 지칭합니다.
요소 타입은
TPair<KeyType, ElementType>이지만, TPair 타입을 직접 참조해야 하는 경우는 드물 것입니다.TMap 키는 고유합니다.
TArray와 마찬가지로 TMap은 동질성 컨테이너입니다. 즉, 모든 요소가 엄격하게 동일한 타입입니다.
TMap은 값 타입이며 일반적인 복사, 할당 및 소멸자 연산은 물론 맵이 소멸될 때 소멸하는 요소의 강력한 소유권을 지원합니다. 키와 값도 값 타입이어야 합니다.
TMap은 해싱 컨테이너이므로 키 타입이 GetTypeHash 함수를 지원해야 하며 키가 동일한지 비교하기 위한
operator==를 제공해야 합니다.
많은 언리얼 엔진 컨테이너와 마찬가지로 TMap 및 TMultimap은 요소 타입이 평범하게 재배치할 수 있다고 가정하는데, 이는 원시 바이트를 직접 복사하여 메모리 내 한 위치에서 다른 위치로 요소를 안전하게 이동할 수 있다는 뜻입니다.
TMultiMap 개요
여러 개의 동일한 키 저장을 지원합니다.
기존 쌍과 일치하는 키가 있는 TMap에 새 키-값 쌍을 추가하면, 새 쌍이 이전 쌍을 대체합니다.
TMultiMap에서 컨테이너는 새 쌍과 이전 쌍을 모두 저장합니다.
TMap은 옵션으로 얼로케이터를 받아 메모리 할당 행동을 제어할 수 있습니다. 하지만, TArray와 달리 이 얼로케이터는 FHeapAllocator나 TInlineAllocator와 같은 표준 언리얼 얼로케이터가 아닌 세트 얼로케이터입니다. 세트 얼로케이터(Set Allocators)(TSetAllocator)는 맵이 사용할 해시 버킷 수와 해시 및 요소 저장에 사용할 표준 UE 얼로케이터를 정의합니다.
마지막 TMap 템플릿 파라미터는 KeyFuncs로, 요소 타입에서 키를 얻는 방법, 두 키의 동일성을 비교하는 방법, 키를 해싱하는 방법을 맵에 알려줍니다. 디폴트는 키에 대한 레퍼런스를 반환한 다음, operator==를 사용하여 동일성을 확인하고 멤버가 아닌 GetTypeHash 함수를 호출하여 해싱하는 것입니다. 키 타입이 이러한 함수를 지원한다면 커스텀 KeyFuncs를 제공하지 않고도 맵 키로 사용할 수 있습니다.
TArray와 달리 메모리에서 TMap 요소의 상대적 순서는 신뢰할 수 없거나 안정적이지 않으며, 요소를 반복작업하면 추가된 순서와 다른 순서로 반환될 가능성이 높습니다. 요소는 메모리에 연속적으로 배치되지 않을 가능성이 높습니다.
맵의 베이스 데이터 구조체는 요소 사이의 간격을 효율적으로 지원하는 배열인 희소 배열입니다. 맵에서 요소가 제거되면 희소 배열의 간격이 나타납니다. 그런 다음, 배열에 새 요소를 추가하면 이러한 간격을 메울 수 있습니다. 그러나, TMap이 간격을 채우기 위해 요소를 셔플하지 않더라도 스토리지가 가득 차고 새 요소가 추가되면 전체 스토리지가 재할당될 수 있으므로 맵 요소에 대한 포인터는 여전히 무효화될 수 있습니다.
맵 생성 및 채우기
다음은 TMap을 생성하는 코드입니다.
TMap<int32, FString> FruitMap;FruitMap은 이제 integer 키로 식별되는 빈 스트링 TMap입니다. 얼로케이터도 KeyFuncs도 지정하지 않았으므로, 맵은 표준 힙 할당을 수행하고 operator==를 사용하여 int32 타입의 키를 비교하고 GetTypeHash를 사용하여 키를 해싱합니다. 이 시점에서는 메모리가 할당되지 않았습니다.
Add
맵을 채우는 표준 방식은 키와 값을 포함하여 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" }
// ]맵에는 여전히 요소가 세 개 있지만, 키가 2인 기존의 Grapefruit 값이 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와 달리 맵은 실행인자가 하나인 생성자로만 요소를 Emplace할 수 있습니다.
Append
실행인자 맵에서 모든 요소를 호출 오브젝트 맵으로 옮기는 Append 함수를 사용하여 두 맵을 병합할 수 있습니다.
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를 비우는 것과 동일합니다. 즉, FruitMap2의 요소 중 이미 FruitMap에 있는 요소와 키를 공유하는 모든 요소는 해당 요소를 대체합니다.
UPROPERTY 매크로와 '편집 가능' 키워드(EditAnywhere, EditDefaultsOnly 또는 EditInstanceOnly) 중 하나를 TMap에 마킹하면, 에디터에서 요소를 추가하고 편집할 수 있습니다.
UPROPERTY(EditAnywhere, Category = MapsAndSets)
TMap<int32, FString> FruitMap;반복작업
TMap을 통한 반복작업은 TArray와 비슷합니다. 요소 타입이 TPair임을 기억하고 C++ 범위 기반 for 문을 사용하면 됩니다.
for (auto& Elem : FruitMap)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
Elem.Key,
*Elem.Value
)
);
}
CreateIterator 및 CreateConstIterator 함수를 사용하여 이터레이터를 생성할 수 있습니다.
| Function | 설명 |
|---|---|
| 읽기-쓰기 액세스 권한이 있는 이터레이터를 반환합니다. |
| 읽기 전용 이터레이터를 반환합니다. |
두 경우 모두 이러한 이터레이터의 Key 및 Value 함수를 사용하여 요소를 검사할 수 있습니다. 이터레이터를 사용하여 예시 FruitMap의 콘텐츠를 출력하려면 다음과 같이 합니다.
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
)
);
}Get Value
맵에 특정 키가 포함되어 있다는 것을 알고 있다면, 키를 인덱스로 사용하여 operator[]로 해당 값을 조회할 수 있습니다. const가 아닌 맵으로 이 작업을 수행하면 const가 아닌 레퍼런스가 반환되고, const 맵은 const 레퍼런스가 반환됩니다.
항상 맵에 키가 포함되어 있는지 확인한 다음 operator[]를 사용해야 합니다. 맵에 키가 포함되어 있지 않으면 어서트됩니다.
FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
FString Val8 = FruitMap[8];
// Assert!Query
현재 TMap에 있는 요소의 수를 확인하려면 Num 함수를 호출합니다.
int32 Count = FruitMap.Num();
// Count == 6맵에 특정 키가 포함되어 있는지 여부를 확인하려면 Contains 함수를 호출합니다.
bool bHas7 = FruitMap.Contains(7);
bool bHas8 = FruitMap.Contains(8);
// bHas7 == true
// bHas8 == false맵에 키가 포함되어 있는지 확실하지 않다면 Contains 함수를 사용하여 확인한 다음 operator[]를 사용하면 됩니다. 그러나, 검색에 성공하려면 동일한 키를 두 번 조회해야 하므로 이 방법은 최적과는 거리가 멉니다.
Find 함수는 이러한 행동을 한 번의 조회로 결합합니다. Find는 맵에 키가 포함되어 있으면 요소의 값에 대한 포인터를 반환하고, 포함되어 있지 않으면 null 포인터를 반환합니다. const 맵에서 Find를 호출하면 const 포인터가 반환됩니다.
FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
// Ptr8 == nullptr또는 쿼리에서 유효한 결과를 얻으려면 FindOrAdd 또는 FindRef를 사용하면 됩니다.
| Function | 설명 |
|---|---|
| 제공한 키와 연결된 값에 대한 레퍼런스를 반환합니다. 키가 맵에 없다면,
|
| 이름과 달리 키와 연결된 값의 사본을 반환하거나 맵에서 키를 찾을 수 없는 경우 디폴트로 생성된 값을 반환합니다. |
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" }
// ]
예시에서 Ref8을 초기화할 때처럼 FindOrAdd가 맵에 새 항목을 추가할 수 있으므로, 이전에 얻은 포인터나 레퍼런스가 유효하지 않게 될 수 있습니다. 이는 새 요소를 포함하기 위해 맵의 백엔드 스토리지를 확장해야 하는 경우 메모리를 할당하고 기존 데이터를 이동하는 추가 연산의 결과입니다. 위의 예시에서 Ref7은 FindOrAdd(8) 호출 후 Ref8 이후에 유효하지 않을 수 있습니다.
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' 부분은 맵 호출에서 키가 존재하지 않는지 확인한다는 의미입니다.
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은 항상 최대한 슬랙을 많이 남겨둡니다.
정렬
키 또는 값별로 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 == []Slack
슬랙은 요소를 포함하지 않는 할당된 메모리입니다. Reserve를 호출하여 요소를 추가하지 않고 메모리를 할당할 수 있으며, Reset을 호출하거나 0이 아닌 슬랙 파라미터로 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" },
위 코드에서 Shrink는 유효하지 않은 요소 하나만 제거했는데, 이는 끝에 빈 요소가 하나만 있었기 때문입니다. 모든 슬랙을 제거하려면 먼저 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
타입에 operator==와 멤버가 아닌 GetTypeHash 오버로드가 있는 한, 변경사항 없이 TMap의 키 타입으로 사용할 수 있습니다. 하지만, 이러한 함수에 오버로드하지 않고 타입을 키로 사용하는 것이 좋을 수도 있습니다. 이러한 경우, 커스텀 KeyFuncs를 제공할 수 있습니다. 키 타입에 대한 KeyFuncs를 생성하려면, 다음과 같이 두 개의 typedef와 세 개의 스태틱 함수를 정의해야 합니다.
| 타입 정의 | 설명 |
|---|---|
| 키를 전달하기 위해 사용하는 타입입니다. |
| 요소를 전달하기 위해 사용하는 타입입니다. |
| Function | 설명 |
|---|---|
| 요소의 키를 반환합니다. |
|
|
|
|
KeyInitType 및 ElementInitType은 키 타입과 요소 타입의 일반적인 전달 규칙을 위한 typedef입니다. 일반적으로 중요하지 않은 타입의 경우 값이 되고, 중요한 타입의 경우 const 레퍼런스가 됩니다. 맵의 요소 타입은 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와 연산자==는 여기에서는 적절하지 않은데, 왜냐하면 operator==는 일반적인 용도로 사용되는 타입의 데이터를 무시해서는 안 되지만, 그와 동시에 UniqueID 필드만 살펴보는 GetTypeHash 행동과 일관성을 위해서는 그렇게 해야 하기 때문입니다.
FMyStruct를 위한 커스텀 KeyFuncs를 생성하는 단계는 다음과 같습니다.
BaseKeyFuncs를 상속합니다.KeyInitType과ElementInitType을 포함해서 유용한 타입을 몇 가지 정의하기 때문입니다.BaseKeyFuncs는 두 개의 템플릿 파라미터를 받습니다.맵의 요소 타입
모든 맵과 마찬가지로 요소 타입은
TPair이며,FMyStruct를KeyType으로,TMyStructMapKeyFuncs의 템플릿 파라미터를ValueType으로 받습니다. 대체KeyFuncs는 템플릿이므로,FMyStruct를 키로 사용하는 TMap을 생성할 때마다 새KeyFuncs를 정의할 필요 없이 맵별로ValueType을 지정할 수 있습니다.
키의 타입
두 번째
BaseKeyFuncs실행인자는 키의 타입으로, 요소 저장소의 키 필드인 TPair의KeyType과 혼동하면 안 됩니다. 이 맵은FMyStruct의UniqueID를 키로 사용해야 하므로, 여기서는FString을 사용합니다.
세 가지 필수
KeyFuncs스태틱 함수를 정의합니다.첫 번째 스태틱 함수는 주어진 요소 타입에 대한 키를 반환하는 GetSetKey입니다. 요소 타입은
TPair이고 키는UniqueID이므로, 이 함수는 간단하게UniqueID를 직접 반환할 수 있습니다.두 번째 스태틱 함수는
GetSetKey로 얻은 두 개의 요소의 키를 받아 서로 비교하여 같은지 확인하는 Matches입니다.FString의 경우, 표준 동등성 테스트(operator==)는 대소문자를 구분하지 않으므로, 이를 대소문자를 구분하는 검색으로 대체하려면 적절한 대소문자 비교 옵션과 함께Compare()함수를 사용합니다.세 번째 스태틱 함수는 추출된 키를 받아 해시 값을 반환하는
GetKeyHash입니다.Matches함수는 대소문자를 구분하므로GetKeyHash도 대소문자를 구분해야 합니다. 대소문자를 구분하는 FCrc 함수는 키 스트링에서 해시 값을 계산합니다.
이제 구조체가 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타입에 필요하기 때문입니다.
자체 KeyFuncs를 제공할 때, TMap은 Matches와 동일한 것으로 비교되는 두 아이템은 GetKeyHash로부터도 동일한 값을 반환한다고 가정한다는 점을 유의해야 합니다. 또한, 이러한 함수 중 하나의 결과를 변경하는 방식으로 기존 맵 요소의 키를 수정하는 것은 맵의 내부 해시를 무효화하므로 정의되지 않은 행동으로 간주됩니다. 이러한 규칙은 디폴트 KeyFuncs를 사용할 때 operator== 및 GetKeyHash의 오버로드에도 적용됩니다.
기타
CountBytes 및 GetAllocatedSize 함수는 내부 배열이 현재 사용 중인 메모리 양을 추정합니다. CountBytes는 FArchive 파라미터를 사용하지만, GetAllocatedSize는 그렇지 않습니다. 이러한 함수는 일반적으로 통계 보고에 사용됩니다.
Dump 함수는 FOutputDevice를 받아 맵의 콘텐츠에 대한 일부 구현 정보를 기록합니다. 이 함수는 보통 디버깅에 사용됩니다.