Choose your operating system:
Windows
macOS
Linux
언리얼 엔진 4
(UE4)에서
TArray
(배열) 다음으로 가장 자주 사용되는 컨테이너는
TMap
(맵)입니다.
TMap
이
TSet
(세트)와 비슷한 점은 그 구조하 키 해시 기반이라는 점입니다. 그러나
TSet
와 달리 이 컨테이너는 데이터를 키-값 짝으로 (
TPair<KeyType, ValueType>
) 저장하며, 저장 및 불러올 때는 키만 사용합니다.
맵의 유형은 두 가지,
TMap
및
TMultiMap
입니다. 이 둘의 차이점은
TMap
키는 고유한 반면,
TMultiMap
은 다수의 동일한 키 저장을 지원합니다. 기존 짝과 일치하는 키로 새 키-값 짝을
TMap
에 추가하면 기존 것이 대체되고,
TMultiMap
에 추가하면 새로 저장합니다.
`TMap`
TMap
에서 키-값 짝은 마치 개별 오브젝트인 것처럼 맵의 엘리먼트 유형으로 정의됩니다. 이 문서에서 element (요소, 엘리먼트)란 키-값 짝을 뜻하는 반면, 개별 컴포넌트는 그 엘리먼트의 키 또는 값 중 하나를 말합니다. 엘리먼트 유형은 실제
TPair< KeyType, ElementType >
로,
TPair
유형을 직접 가리키는 것은 드물긴 합니다.
TArray
처럼
TMap
역시 동질성 컨테이너로, 그 엘리먼트 전부 엄격히 같은 유형입니다.
TMap
은 값 우형이기도 하여, 일반적인 복사, 할당, 소멸 연산이 지원될 뿐만 아니라, 그 엘리먼트에 대한 강 소유권도 지원되어 맵이 소멸되면 같이 소멸되기도 합니다. 키 유형과 값 유형은 반드시 값 유형이어야 합니다.
TMap
은 해시 컨테이너라서, 기본적으로 키 유형은 반드시
GetTypeHash
를 지원하고 키의 동일성 비교를 위한
operator==
가 제공되어야 한다는 뜻입니다. 해시에 대해서는 나중에 자세히 다루겠습니다.
TMap
은 메모리 할당 방식 제어를 위한 옵션 얼로케이터를 받기도 합니다. 그러나
TArray
와 달리 이들은 세트 얼로케이터를 사용하며, 표준 UE4 얼로케이터를 (예:
FHeapAllocator
,
TInlineAllocator
) 사용할 수 없습니다. 세트 얼로케이터 (
TSetAllocator
클래스)는 맵에 해시 버킷을 얼마나 사용할지와 어떤 표준 UE 얼로케이터를 사용해서 해시 및 엘리먼트를 저장할지를 정의합니다.
마지막
TMap
템플릿 파라미터는
KeyFuncs
로, 엘리먼트 유형에서 키를 구하는 방법, 두 키 사이의 동등성을 비교하는 방법, 키에 대한 해싱 방법을 맵에 알려주는 것입니다. 이에 대한 기본값은 그냥 키에 대한 레퍼런스 반환, 동등성에는
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" }
// ]
맵에는 여전이 엘리먼트가 세 개 있지만, 이전에 키가 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
와는 달리 단일 인수 생성자로 맵속에 엘리먼트를 Emplace 시키는 것만 가능합니다.
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" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
// 이제 FruitMap2 은 비었습니다.
위 예제에서 결과 맵은
Add
또는
Emplace
를 사용해
FruitMap2
각 엘리먼트를 개별 추가하고, 프로세스가 완료되면
FruitMap2
를 비운 것과 같습니다. 즉
FruitMap2
의 엘리먼트 중 기존
FruitMap
의 엘리먼트 키를 공유하는 것이 있으면 그 엘리먼트를 대체한다는 뜻입니다.
TMap
에
UPROPERTY
매크로와 편집가능 키워드 (
EditAnywhere
,
EditDefaultsOnly
,
EditInstanceOnly
) 중 하나를 마킹하면,
언리얼 에디터에서 엘리먼트를 추가 및 편집
가능합니다.
UPROPERTY(Category = MapsAndSets, EditAnywhere)
TMap<int32, FString> FruitMap;
반복처리
TMap
에 대한 iteration(반복처리)는
TArray
와 유사합니다. 엘리먼트 유형이
TPair
임을 기억하고, C++ 의 범위 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
함수를 사용해서 엘리먼트를 조사할 수 있습니다. 다음은 이터레이터를 사용해서 예제 "fruit" 맵의 내용을 출력한 것입니다:
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[]
로 해당 값을 조회하면 됩니다. non-const 맵은 non-const 레퍼런스를, const 맵은 const 레퍼런스를 반환합니다.
operator[]
FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
FString Val8 = FruitMap[8];
// 어서트!
맵에 키가 들어있는지 확실하지 않다면,
Contains
함수에
operator[]
를 사용하면 됩니다. 그러나 이는 이상적이지 않은데, 불러오기에 성공해도 같은 키를 두 번 조회해야 하기 때문입니다.
Find
함수는 이 동작을 한 번의 조회로 합칩니다.
Find
는 맵에 키가 들어있으면 엘리먼트 값으로의 포인터를, 없으면 널 포인터를 반환합니다.
Find
를 const 맵에 호출하면 반환하는 포인터도 const 가 됩니다.
FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
// Ptr8 == nullptr
또는 쿼리 결과 유효성을 보장하려면,
FindOrAdd
또는
FindRef
를 사용하면 됩니다.
FindOrAdd
는 제공한 키에 연관된 값으로의 레퍼런스를 반환합니다. 키가 맵에 있지 않은 경우,
FindOrAdd
는 새로 생성된 엘리먼트에 키와 기본 생성된 값을 반환하고, 맵에 추가합니다. 이게 맵을 수정할 수도 있기 때문에,
FindOrAdd
는 non-const 맵에만 사용할 수 있습니다.
FindRef
는 그 이름과 달리 키에 연관된 값, 맵에 그 키가 없는 경우 기본 생성된 값 사본을 반환합니다.
FindRef
는 새 엘리먼트를 생성하지 않으므로, const 및 non-const 맵 양쪽에서 사용 가능합니다.
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: "" }
// ]
우리 예제의
Ref8
초기화에서 한 것처럼
FindOrAdd
는 맵에 새 항목을 추가할 수 있으므로, 이전에 (
Find
에서) 얻은 포인터 또는 (
FindOrAdd
에서 얻은) 레퍼런스는 유효성이 상실될 수 있습니다. 새 엘리먼트의 저장을 위해 맵의 백엔드 스토리지를 확장시켜야 하는 경우 추가로 발생하는 메모리 할당 및 기존 데이터 이동 작업의 결과입니다. 위 예제에서
Ref7
는
FindOrAdd(8)
호출 이후
Ref8
이후에 유효성을 상실할 수 있습니다.
FindKey
함수는 역조회, 즉 제공된 값에 일치하는 키를 검색하여 제공된 값의 짝인 첫 키로의 포인터를 반환합니다. 맵에 있지 않은 값을 검색하면 널 키를 반환합니다.
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" 부분은 키가 존재하지 않으면 맵이 (UE4 에서
assert
에 해당하는)
check
를 호출함을 나타냅니다.
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);
// 어서트!
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
은 정규 값 유형이므로, 표준 복사 생성자나 할당 연산자를 통해 복사 가능합니다. 맵은 자신의 엘리먼트를 엄격히 소유하므로, 맵을 복사하면 심도가 유지되어(deep) 새 맵은 별도의 엘리먼트 사본을 갖게 됩니다.
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 == []
슬랙
Slack (여유분, 슬랙)은 할당된 메모리에 엘리먼트가 없는 것을 말합니다. 엘리먼트 없이 메모리를 할당하려면
Reserve
를 호출하면 되며, 메모리 할당을 해제하지(deallocate) 않고 엘리먼트를 제거하는 것도
Reset
호출 또는
Empty
에 0 이 아닌 슬랙 파라미터로 호출하면 됩니다. 메모리 할당 해제할 필요가 없으니 엘리먼트 제거에도 도움이 됩니다. 특히 맵을 비우고 엘리먼트 수가 같거나 적은 맵으로 바로 다시 채우려는 경우 특히 효율적입니다.
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" },
// { 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" }
// ]
위 코드에서
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>,
// <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
를 제공해 주면 됩니다. 키 유형에 대해
KeyFunc
를 만들려면, 다음과 같이 두 개의 typedef 및 세 개의 static 함수 정의가 필요합니다:
-
KeyInitType
- 키 전달에 사용됩니다. -
ElementInitType
- 엘리먼트 전달에 사용됩니다. -
KeyInitType GetSetKey(ElementInitType Element)
- 엘리먼트의 키를 반환합니다. -
bool Matches(KeyInitType A, KeyInitType B)
- A 와 B 가 동일하면true
, 아니면false
를 반환합니다. -
uint32 GetKeyHash(KeyInitType Key)
- 키의 해시 값을 반환합니다. 보통 외부 GetTypeHash 함수를 호출합니다.
KeyInitType
및
ElementInitType
은 키 유형과 엘리먼트 유형의 일반 전달 규칙에 대한 typedef 입니다. 보통 이들은 사소한(trivial) 유형에 대해서는 값이, 사소하지 않은 유형에 대해서는 const 레퍼런스가 됩니다. 맵의 엘리먼트 유형은
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
에는 고유 식별자는 물론, 정체성에 기여하지 않는 몇 가지 다른 데이터도 들어 있습니다.
GetTypeHash
및
operator==
는 여기 적합하지 않을 텐데, 왜냐면
operator==
는 일반적인 용도로 사용되는 유형의 데이터를 무시해서는 안되는 반면 그와 동시에
GetTypeHash
동작, 즉
UniqueID
필드만 살펴 보는 동작 일관성을 위해서는 필요합니다. 다음 단계는
FMyStruct
에 대한 커스텀
KeyFuncs
생성에 도움이 됩니다:
-
먼저
BaseKeyFuncs
를 상속합니다.KeyInitType
와ElementInitType
을 포함해서 유용한 것을 몇 가지 정의해 주기 때문입니다.BaseKeyFuncs
는 두 가지 템플릿 파라미터, 맵의 엘리먼트 유형과 키 유형을 받습니다. 모든 맵과 마찬가지로 엘리먼트 유형은TPair
이며,FMyStruct
를KeyType
으로,TMyStructMapKeyFuncs
를ValueType
으로 받습니다. 대체KeyFuncs
는 템플릿이므로,FMyStruct
에 키 설정된TMap
을 생성하려 할 때마다KeyFuncs
를 새로 정의하기 보다는, 맵 단위로ValueType
을 지정할 수 있습니다. 두 번째BaseKeyFuncs
인수는 키 유형이며, 엘리먼트가 저장하는 Key 필드인TPair
의KeyType
과는 다릅니다. 이 맵은 (FMyStruct
의)UniqueID
를 키로 사용해야 하므로, 여기에는FString
이 사용됩니다. -
다음, 필수
KeyFuncs
스태틱 함수 셋을 정의합니다. 첫째는GetSetKey
로, 엘리먼트 유형을 주면 키를 반환합니다. 엘리먼트 유형은TPair
, 키는UniqueID
이므로, 함수는UniqueID
를 직접 반환할 수 있습니다.두 번째 스태틱 함수는
Matches
로, (GetSetKey
로 불러온) 두 엘리먼트의 키를 받아, 그 둘을 비교하여 동등성 검사를 합니다.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
를 사용해서 동등성 검사를 하는 두 항목이
GetKeyHash
에서와 같은 값을 반환한다 가정합니다. 게다가 이 함수들 중 어느 하나의 결과를 바꾸는 방식으로 기존 맵 엘리먼트의 키를 변경하는 것은 정의되지 않은 동작으로 간주되는데, 그렇게 되면
TMap
의 내부 해시가 무효화되기 때문입니다. 이 규칙은 기본
KeyFuncs
를 사용할 때
operator==
와
GetKeyHash
기타
CountBytes
와
GetAllocatedSize
함수는 현재 내부 배열에 사용되는 메모리 양을 측정합니다.
CountBytes
는
FArchive
파라미터를 받는 반면
GetAllocatedSize
는 아닙니다. 이 함수들은 일반적으로 통계 보고에 사용됩니다.
Dump
함수는
FOutputDevice
를 받아 맵 콘텐츠에 대한 약간의 구현 정보를 출력합니다. 이 함수는 주로 디버깅에 사용됩니다.