C++ STL과 UTL(언리얼 컨테이너 라이브러리)의 차이점
기본적으로 C++의 STL을 게임에 적용해 사용하기에는 그렇게 좋지 않은 성능을 낼 수 있다. 따라서 언리얼 엔진 자체적으로 개발한 컨테이너 라이브러리에 대해 알아보자.
둘의 차이점은 다음과 같다.
C++ STL | UTL |
범용적으로 설계되어 있다. 따라서 게임 오브젝트와의 연동이 안될 수도 있다. | 언리언 엔진에 특화되어 있다. |
표준이기 때문에 호환성이 높다. | 언리얼 오브젝트 구조를 안정적으로 지원한다. |
많은 기능이 엮여 있어서 컴파일 시간이 오래걸린다. | 가볍고 게임 제작에 최적화가 되어있다. |
우리는 게임을 개발하면서 많은 자료구조를 사용할테지만 그 중 대부분이
TArry, TSet, TMap을 사용할 것이다.
TArray는 오브젝트를 순서대로 담아 효율적으로 관리하는 용도로 C++ STL의 Vector와 용도가 비슷하다.
TSet은 중복되지 않는 요소로 구성된 집합을 만드는 용도소, C++ STL에서 Set과 유사하지만, 동작원리는 unordered_set과 비슷하다.
TMap은 키, 밸류 조합의 레코드를 관리하는 용도로, C++ STL의 Map과 유사하지만 동작원리는 unordered_map과 비슷하다.
TArray
TArray의 특징을 알아보자.
- 동작 할당을 불가하고 가변 배열이다.(new / delete를 사용하지 않는 다는 뜻)
- 데이터가 순차적으로 모여있어서 효과적 사용이 가능하다.
- 캐시 효율이 높다.
- 배열의 맨 끝 추가는 가볍지만 중간에 요소 추가 및 삭제는 비용이 크다.
자주 쓰이는 함수
Add | 밖에서 생성 후 복사해서 맨 끝 요소에 삽입 |
Emplace | Add와 같이 맨 끝 요소에 삽입 하지만 복사가 아닌 내부에서 바로 생성 |
SetNum | 배열 크기 조정 |
Find | 순회하며 요소 찾음. 효율이 좋지 않을 수 있다. |
MoveTemp | 배열 데이터 통째로 이주 |
== | 요소 자료형과 배열의 크기가 같을 때의 비교 연산자. FString 시 대소문자 구분 X |
Sort | 정렬 |
const int32 ArrayNum = 10;
TArray<int32> Int32Array; # TArray 선언
for (int32 ix = 1; ix <= ArrayNum; ++ix)
{
Int32Array.Add(ix);
}
Int32Array.RemoveAll(
[](int32 Val)
{
return Val % 2 == 0; # RemoveAll 함수에서 Val 전달
}
);
Int32Array += {2, 4, 6, 8, 10};
TArray<int32> Int32ArrayCompare;
int32 CArray[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8 , 10 };
Int32ArrayCompare.AddUninitialized(ArrayNum); # 배열 크기 설정
FMemory::Memcpy(Int32ArrayCompare.GetData(), CArray, sizeof(int32) * ArrayNum); # Memcpy 를 통한 배열 복사
ensure(Int32Array == Int32ArrayCompare);
int32 Sum = 0;
for (const int32& Int32Elem : Int32Array) # 읽기 전용 순회
{
Sum += Int32Elem;
}
ensure(Sum == 55);
int32 SumByAlgo = Algo::Accumulate(Int32Array, 0); # sum 작용을 하는 accumulate함수가 #include "Algo/Accumulate.h" 에 포함되어 있다.
ensure(Sum == SumByAlgo);
슬랙
TArray에는 슬랙이라는 개념이 적용된다.
배열을 선언하고 엘리먼트를 추가하면 넉넉하게 메모리를 할당 받는다는 말이다. 10개의 엘리먼트를 추가한다고 메모리의 크기가 10개가 아니라는 말인 셈이다. 따라서 엘리먼트를 삭제시 메모리가 바로 해제되진 않는다. Shrink 함수로 슬랙 공간을 제거 할 수 있다. 슬랙 공간을 이용하면 최적화에 대한 힌트를 얻을 수 있다.
가령 예를 들어 배열에 100개의 요소를 더 추가해야하는 상황에, 슬랙이 100이상 된다는 것을 경험적으로 알고 있다면, 배열의 크기를 늘릴 필요 없이 그냥 바로 추가하는 것이다.
TSet
TSet은 위에서 STL의 Set과 유사하지 동일하지 않다고 했는데 차이는 다음과 같다.
C++ STL Set | 언리얼 TSet |
이진 트리로 구성되어 있어 정렬을 지원한다. | 해시테이블 형태로 키 데이터가 구축되어 있어 빠른 검색이 가능하다. O(1) |
메모리 구성이 효율적이지 않다. | 동적 배열의 형태로 데이터가 모여있음. |
요소가 삭제될 때 균형을 위한 재구축이 일어날 수 있음. | 데이터를 삭제해도 재구축이 일어나지 않음 |
모든 자료를 순회하는 데 적합하지 않다. | 비어있는 데이터가 있을 수 있다. (Invalid) |
TSet의 가장 중요한 특징 중 하나는 빈 공간 Invalid한 공간이 생길 수 있다는 점이다.
만약 우리가 10개의 엘리먼트를 Add하거나 emplace하고 remove를 했을 경우 TSet의 크기는 변하지 않고 그 공간은 Invalid한 공간으로 남겨둔다. 그리고 새로운 엘리먼트가 추가 되었을 때, Invalid한 공간에 그 엘리먼트를 집어넣으므로써 효율적이게 동작한다는 점이다. 따라서 STL의 Set과 다르게 정렬이 되어있지 않고 순서가 보장되지 않는다.
이는 Compact함수를 통해 Invalid한 공간을 없앨 수 있다.
우리가 새로운 구조체를 사용해 TSet를 만들고 싶다면 해당 자료형에 대한 GetTypeHash 함수를 만들어줘야만 한다. 다만 FString이나 int, UObject와 같은 값들은 이미 언리얼엔진에서 해싱 값들을 만들어서 가지고 있다.
#include "MyGameInstance.h"
#include "Algo/Accumulate.h"
void UMyGameInstance::Init()
{
TSet<int32> Int32Set;
for (int32 i = 1; i <= 10; i++) {
Int32Set.Add(i);
}
// Remove All처럼 한번에 다 지우는 함수는 없다. 아래와 같이 하나씩 지워줘야 한다.
Int32Set.Remove(2);
Int32Set.Remove(4);
Int32Set.Remove(6);
Int32Set.Remove(8);
Int32Set.Remove(10);
}
'Unreal Engine 5 > 강의' 카테고리의 다른 글
[UE5] 언리얼 오브젝트 관리- 직렬화, 패키지 (0) | 2024.11.27 |
---|---|
[UE5] 언리얼 컨테이너 라이브러리 - Struct와 TMap, 언리얼 메모리 관리 (0) | 2024.11.25 |
[UE5] 언리얼 C++ 설계 - 인터페이스, 컴포지션, 델리게이트 (0) | 2024.11.21 |
[UE5] 언리얼 오브젝트의 이해 - 리플렉션 시스템 (3) | 2024.11.20 |
[UE5] 언리얼 오브젝트의 이해 - 코딩 표준, 기본 타입과 문자열, 언리얼 오브젝트란? (0) | 2024.11.19 |