언리얼 C++ 프로그래밍

[언리얼 C++] 컨테이너 클래스 TSet 사용법(연산자, 슬랙, DefaultKeyFuncs 지정)

언린이 2021. 7. 15. 21:00

TSet 컨테이너 클래스의 기본적인 설명과 간단한 사용법은 [언리얼 C++] 컨테이너 클래스 TSet 사용법(생성 및 삽입, 반복처리) (tistory.com) 글을 참고하시기 바랍니다.

이 글에서는 TSet 컨테이너 클래스의 연산자, 슬랙, DefaultKeyFuncs에 대해 알아보겠습니다.

 

 

1. TSet 컨테이너 연산자

TSet<FString> NewSet = FruitSet;
NewSet.Add(TEXT("Apple"));
NewSet.Remove(TEXT("Pear"));
// FruitSet = { "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" }
// NewSet = { "Kiwi", "Melon", "Mango", "Orange", "Grapefruit", "Apple" }

TSet 컨테이너는 TArray 컨테이너와 마찬가지로 정규값 유형이므로 표준 복사 생성자 또는 할당 연산자를 통해 복사할 수 있습니다. TSet 컨테이너는 엘리먼트를 엄격하게 소유하므로, TSet 컨테이너를 새로운 TSet 컨테이너에 복사하면 새로운 TSet 컨테이너는 엘리먼트 별도의 사본을 가지게 됩니다.

 

 

2. TSet 컨테이너 슬랙 지원

슬랙은 할당된 메모리에 엘리먼트가 없는 것을 말합니다. 엘리먼트 없이 메모리를 할당하려면 Reserve() 함수를 호출하면 되며, 메모리 할당을 해제하지 않고 엘리먼트를 제거하는 것도 Reset() 함수 호출 또는 Empty() 함수에 0이 아닌 슬랙 파라미터를 넘겨 호출하면 됩니다. 메모리 할당 해제할 필요가 없으니 엘리먼트 제거에도 도움이 됩니다. 특히 TSet 컨테이너를 비우고 엘리먼트 수가 같거나 적은 TSet 컨테이너로 바로 다시 채우려는 경우 특히 효율적입니다.

 

FruitSet.Reserve(10);
for (int32 i = 0; i < 10; ++i)
{
    FruitSet.Add(FString::Printf(TEXT("Fruit%d"), i));
}
// FruitSet = { "Fruit9", "Fruit8", "Fruit7" ... "Fruit2", "Fruit1", "Fruit0" }

Reserve() 함수를 사용하여 슬랙을 직접 만들 수 있습니다.

이런식으로 Reserve() 함수를 사용하여 엘리먼트를 추가하기 전 메모리를 미리 할당할 수 있습니다.

 

FruitSet.Reset();
// FruitSet = { <invalid>, <invalid>, <invalid>, <invalid>, <invalid>, <invalid> }

Reset() 함수를 사용하여 메모리 할당 해제 없이 TSet 컨테이너의 모든 엘리먼트를 제거하여 슬랙을 만들 수 있습니다.

 

// 세트에서 엘리먼트를 하나 건너 하나씩 제거합니다.
for (int32 i = 0; i < 10; i += 2)
{
    FruitSet.Remove(FSetElementId::FromInteger(i));
}
// FruitSet = {"Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0", <invalid> }

FruitSet.Shrink();
// FruitSet = {"Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0" }

TSet 컨테이너의 모든 슬랙을 제거하려면, Collapse() 함수 및 Shrink() 함수를 사용하면 됩니다.

Shrink() 함수는 TSet 컨테이너 끝에서부터 모든 슬랙을 제거하지만, 중간이나 시작 부분의 슬랙은 제거하지 않습니다.

그래서 위 예제 코드에선 Shrink() 함수를 통해서 단 하나의 슬랙만 제거되었습니다. 끝에 존재하는 슬랙이 하나뿐이었기 때문입니다.

 

FruitSet.CompactStable();
// FruitSet = {"Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0", <invalid>, <invalid>, <invalid>, <invalid> }
FruitSet.Shrink();
// FruitSet = {"Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0" }

TSet 컨테이너의 모든 슬랙을 끝으로 몰 수 있다면 Shrink() 함수를 통해 모든 슬랙을 제거할 수 있을 것입니다.

이를 위해서 TSet 컨테이너 클래스는 Compact() 함수와 CompactStable() 함수를 제공해줍니다.

위 예제 코드에서 CompactStable() 함수를 호출하여 모든 슬랙을 컨테이너의 끝으로 몰고, Shrink() 함수를 호출하여 컨테이너의 끝에 존재하는 슬랙들을 제거하였습니다. 이렇게 하면 모든 슬랙을 제거할 수 있습니다.

 

 

3. TSet 컨테이너 DefaultKeyFuncs 지정

한 타입에 operator==와 멤버가 아닌 GetTypeHash() 함수 오버로드가 있는 한, 그 타입은 TSet 컨테이너가 사용할 수 있는데, 그 타입이 엘리먼트이기도 하고 Key이기도 하기 때문입니다. 하지만 그러한 함수를 오버로드하는 것이 바람직하지 않은 경우 타입을 Key로 사용하는 것이 좋을 수 있습니다. 이러한 경우, 별도의 커스텀 DefaultKeyFuncs을 제공해주면 됩니다. Key 타입에 대해 DefaultKeyFuncs을 만들려면, 다음과 같이 typedef 두개와 static 함수 정의가 필요합니다.

  • KeyInitType : Key 전달에 사용됩니다. 주로 ElementType 템플릿 파라미터에서 뽑힙니다.
  • ElementInitType : 엘리먼트 전달에 사용됩니다. 마찬가지로, 주로 ElementType 템플릿 파라미터에서 뽑히므로, KeyInitType과 동일합니다.
  • KeyInitType GetSetKey(ElementInitType Element) : 엘리먼트의 Key를 반환합니다. 일반적으로 엘리먼트 자체입니다.
  • bool Matches(KeyInitType A, KeyInitType B) : A 와 B 가 동일하면 true, 아니면 false 를 반환합니다.
  • uint32 GetKeyHash(KeyInitType Key) : Key의 해시 값을 반환합니다. 보통 외부 GetTypeHash() 함수를 호출합니다.

KeyInitType 및 ElementInitType은 Key/엘리먼트 타입의 일반 전달 규칙에 대한 typedef입니다. 보통 이들은 trivial 타입에 대해서는 값이, 그렇지 않은 타입에 대해서는 const 레퍼런스가 됩니다.