언리얼 컨테이너 라이브러리(UCL, Unreal Container Library)
언리얼 엔진이 자체 제작해 제공하는 자료구조 라이브러리
언리얼 오브젝트를 안정적으로 지원하면서 다수의 오브젝트 처리에 사용
실제 게임 제작에 사용되는 라이브러리는 TArray, TSet, TMap (T는 Template Library를 의미)
가볍고 게임 제작에 최적화됨
C++ STL
범용적으로 설계됨
표준이기 때문에 호환성이 높음
많은 기능이 있어 컴파일 시간이 오래 걸림
TArray
STL vector와 유사
시퀀스 컨테이너, 가변 배열 자료구조
데이터가 순차적으로 모여있기 때문에 메모리를 효과적으로 사용할 수 있고 캐시 효율도 높음
컴퓨터 사양이 좋아지면서, 캐시 지역성으로 인한 성능 향상이 중요해짐
임의 데이터 접근이 빠르고, 빠르게 순회 가능
맨 끝에 데이터를 추가하는 것은 가볍지만 중간에 요소를 추가하거나 삭제하는 비용이 큼
데이터가 많아질수록 검색, 삭제, 수정 작업이 느리기 때문에 많은 수의 데이터일 경우 TSet을 고려
멤버 함수
GetData : 배열을 시작하는 부분의 포인터를 가져옴
FString* StrPtr = StrArr.GetData();
Add : 추가할 데이터를 생성한 후 TArray에 이동/복사해서 넣는 방식, 가독성 높음
Emplace : 생성자의 매개변수를 전달하면 TArray 자체에서 바로 생성
Add보다 효율 높음, 가독성 낮음
구조체 이상의 크기를 가진 데이터를 넣을 때 적극 사용
Append, += : TArray / 배열을 추가할 때 사용
AddUnique : 요소에 존재하지 않는 경우에만 추가, 보통 TSet이 더 유용
TArray<FString> StrArr;
StrArr.Add(TEXT("Hello"));
StrArr.Emplace(TEXT("Hello")); // StrArr == ["Hello", "Hello"]
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
StrArr += Arr;
Num() : 배열 Size 반환
SetNum() : 배열의 크기를 설정, 현재 크기보다 작은 경우 요소 제거
GetSlack : 배열의 메모리 여유분을 반환, vector의 capacity와 유사
Shrink : 요소가 없는 Slack을 제거하여 불필요한 메모리 감소
int32 Count = StrArr.Num();
StrArr.SetNum(8);
SlackArray.GetSlack();
SlackArray.Shrink();
Insert : 중간에 추가
Remove : 해당하는 요소를 삭제
StrArr.Insert(TEXT("Brave"), 1);
StrArr.Remove(TEXT("Brave"));
operator[] : 인덱스 접근, 레퍼런스로 반환, 균일한 데이터로 배열되기 때문에 주소를 한번에 알 수 있어서 빠름
대입 연산자를 사용하면 Add 함수처럼 복사
StrArr[0] = TEXT("Hello");
Init : 기본 값을 채우는 함수
AddUninitialized / InsertUninitialized : 초기화되지 않는 메모리 추가, 빠르게 메모리 할당 가능
IntArray.Init(10, 5); // IntArray == [10,10,10,10,10]
int32 SrcInts[] = { 2, 3, 5, 7 };
TArray<int32> UninitInts;
UninitInts.AddUninitialized(4);
FMemory::Memcpy(UninitInts.GetData(), SrcInts, 4*sizeof(int32)); // UninitInts == [2,3,5,7]
Contains : 특정 요소가 들어있는지 확인하는 함수
IsValidIndex : 인덱스가 유효한지 확인하는 함수
bool bIsExistHello = StrArr.Contains(TEXT("Hello"));
bool bValidM1 = StrArr.IsValidIndex(-1); // bValidM1 == false
CreateIterator/CreateConstIterator : Iterator/ConstIterator 반환하는 함수
for (auto It = StrArr.CreateConstIterator(); It; ++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}
Empty : 모든 요소 제거
StrArr.Empty(); // StrArr == []
MoveTemp : 데이터 이동
StrArr1 = MoveTemp(StrArr);
Heapify : 이진 힙 데이터 구조로 변환
TArray<int32> HeapArr;
for (int32 Val = 10; Val != 0; --Val)
{
HeapArr.Add(Val); // HeapArr == [10,9,8,7,6,5,4,3,2,1]
}
HeapArr.Heapify(); // HeapArr == [1,2,4,3,6,5,8,10,7,9]
Top : 배열 끝 요소 반환
== / != : 배열이 같은 경우는 요소 수와 값들이 같을 경우 true
Sort(퀵 정렬로 진행하다가 깊이가 너무 크면 힙 정렬로 전환됨), HeapSort, StableSort(합병 정렬)
TSet
STL set
이진트리로 구성되어 정렬이 자동 적용되므로 불필요한 정렬이 일어날 수 있음
요소가 삭제될 때 균형을 위한 트리 구조 재구축이 일어날 수 있음
모든 요소를 순회하는데 적합하지 않음
TSet
STL unorederd_set과 유사
해시테이블 형태로 키 데이터가 구축되어 추가, 검색, 제거가 빠름
요소가 있는 TArray를 따로 유지하여 빠르게 순회 가능
비어 있는 데이터가 존재 가능하여 요소가 삭제되거나 테이블이 리사이즈될 때 중간에 비어있는 버킷이 있을 수 있음
사용자 정의 타입으로 구성할 경우 GetTypeHash 함수를 정의해야 함
멤버 함수
TArray와 유사
Find는 찾으면 해당 요소 포인터를 반환, 없으면 nullptr 반환
Index : Set의 Index를 의미하는 FSetElementId 반환
추가할 때 마지막 Invalid 부터 채워지므로 순서가 보장되지 않기 때문에 삭제할 때 인덱스로 제거하는 것은 권장 X, 키 값으로 제거
FSetElementId BananaIndex = FruitSet.Index(TEXT("Banana"));
Array : 비어있는 메모리 없이 Array로 변경
TArray<FString> FruitArray = FruitSet.Array();
Reset : 모든 요소를 invalid slack으로 변경
Shrink : 뒤 쪽에 있는 invalid slack을 제거
FruitSet.Reset();
// FruitSet == [ <invalid>, <invalid>, invalid> ] // invalid : 비어있는 slack
// FruitSet == [ <invalid>, "Banana", <invalid> ]
FruitSet.Shrink();
// FruitSet == [ <invalid>, "Banana" ]
TMap
STL Map
STL Set과 동일하게 이진트리로 구성
정렬 자동 적용, 데이터 삭제시 재구축 가능
순회하는데 적합하지 않음
Unreal TMap
TSet과 동일한 구조, 동일한 장단점, 유사한 멤버 함수
TPair<Key, Value>를 가진 튜플 데이터로 구성됨
TMultiMap을 사용하여 중복 데이터를 관리할 수 있음
구조체에서 동일 기준이 2개 이상일 때 MapKeyFuncs 사용
멤버 함수
값으로 키 찾는 함수, 최악의 경우 모든 요소 순회
const int32* KeyMangoPtr = FruitMap.FindKey(TEXT("Mango"));
Key/Value를 TArray로 얻는 함수
TArray<int32> FruitKeys;
TArray<FString> FruitValues;
FruitMap.GenerateKeyArray(FruitKeys);
FruitMap.GenerateValueArray(FruitValues);
TMultimap의 중복된 키를 찾아 TArray에 넣는 함수
const FString TargetName(TEXT("홍길동"));
TArray<int32> AllOrders;
StudentsMapByName.MultiFind(TargetName, AllOrders);
새로운 타입을 정의하여 키로 사용할 경우에 아래 두 함수를 추가해야 함
bool operator==(const FStudentData& InStudentData) const
{
return Order == InStudentData.Order;
}
friend FORCEINLINE uint32 GetTypeHash(const FStudentData& InStudentData)
{
return GetTypeHash(InStudentData.Order);
}
TArray | TSet | TMap | |
접근 | O(1) | O(1) | O(1) |
검색 | O(N) | O(1) | O(1) |
삽입 | O(N) | O(1) | O(1) |
삭제 | O(N) | O(1) | O(1) |
TArray는 비어있는 메모리가 없으므로 탐색 속도가 더 빠름
구조체 UStruct
단순한 데이터 타입에 적합
언리얼 엔진이 일반 객체로 취급하여 리플리케이션을 사용하지 못하지만 UPROPERTY로 선언한 멤버 변수는 가능
GENERATED_BODY를 선언
UScriptStruct 클래스로 구현됨
리플렉션, 직렬화, 데이터 전송 등 가능
C++과 다르게 함수는 선언할 수 없음
USTRUCT()
struct FMyStruct
{
GENERATED_BODY()
UPROPERTY()
TObjectPtr<UObject> SafeObjectPointer; // UPROPERTY로 선언하여 자동으로 메모리 관리
};
'언리얼 > 언리얼 C++ 및 개념' 카테고리의 다른 글
언리얼 오브젝트 직렬화 (0) | 2024.07.20 |
---|---|
언리얼 가비지 컬렉션 (0) | 2024.07.18 |
언리얼 C++ 델리게이트 (0) | 2024.07.11 |
언리얼 C++ 설계 (0) | 2024.07.09 |
언리얼 오브젝트 (0) | 2024.07.04 |