내일배움캠프 39일차 - 언리얼 엔진 C++ 심화 2 : 컨테이너와 포인터

TObjectPtr

 - TObjectPtr은 UE5에서 도입된 템플릿 스마트 포인터이다.
 - UE4원시포인터 UObject*대체하기 위해 사용되었다.

에디터 환경 전용으로 설계되어 두 가지 핵심 기능을 제공한다.

1. 지연로딩: 변수가 실제로 접근될 때만 리소스가 로드되어 시작 시 메모리 과부하를 방지
2. 액세스 트래킹: UPROPERTY변수가 어디서 참조되고 있는지 정밀하게 식별, 지원한다.

TObjectPtr 사용 규칙과 성능

TObjectPtr 권장 사용: 헤더 파일의 UPROPERTY 변수
 : UE5에서 권장하는 표준방식으로, 지연 로딩액세스 트래킹을 완전히 지원한다.
ex)TObjectPtr<UObject> ptr;

※ TObjectPtr을 사용해도 게임성능은 향상되지 않습니다.
 : 패키징(출시) 배포 후의 TObjectPtr원시포인터자동변환된다.



원시 포인터 사용: 지역 변수, 함수 매개변수
 : 이 경우 참조 대상 객체가 이미 메모리에 존재하므로 로딩 로직이 필요없어 원시 포인터가 더 직접적이고 효율적이다.
ex) UObject* ptr;



언리얼 컨테이너

언리얼의 컨테이너는 크게 세가지로, TArray, TSet, TMap이 있다. 각각 vector, set, map의 기능을 한다.

TObjectPtr 컨테이너 순회방법

TObjectPtr 컨테이너를 순회할 때 반복자 타입 선택은 중요하다.

auto* 로 순회: 비권장. 매 반복마다 내부 로드 검사를 실행하여 성능저하가 발생한다.

auto* 순회는 비권장.

auto& 로 순회: 권장. 컨테이너 내 TObjectPtr 자체를 직접 참조하여 내부 로드, 주소 계산 과정을 생략하여 최적의 성능을 발휘한다.

auto& 순회를 권장

TSubclassOf - 클래스 레퍼런스 컨테이너

인스턴스가 아닌 타입을 저장 - '어떤 클래스를 사용할지 엔진에 알려주는 방식'

여기 편집 가능한 세 타입변수가 있다. 내가 여기서 'AMyDebugTestActor' 라는 액터클래스를 에디터에서 참조하고싶다 가정하자. 이 중에서 어떤 타입를 쓰는 것이 적합한지 알아보자.

1) AMyDebugTestActor*

찾고자 하는 클래스를 직접 포인터로 참조한다면 '이미 존재하는 클래스객체'만 가져온다. 즉, 클래스 타입 자체를 참조할 수 없다.

2) UClass*

UClass*를 사용하면 '클래스타입'을 가져올 수는 있다. 하지만 모든 클래스를 가져오게 되므로, 원하는 클래스타입을 찾기 위해 검색이 필수가 되며, 선택하는 과정에서 실수를 유발할 수 있다.

3) TSubclassOf<AMyDebugTestActor>

TSubclassOf를 사용하면 지정한 클래스의 '하위 클래스타입'을 가져올 수 있다. UClass*에 비해 지정클래스만을 참조하므로 그로 인한 실수를 예방할 수 있다.

컨테이너 메모리 레이아웃과 성능 차이

TArray: 연속 메모리 레이아웃 - 캐시 친화적 공간

TSet/TMap: 해시 분산 레이아웃 - 키값 조회 → 매우 빠름


TArray
Vector 기반 · 연속 메모리
TSet / TMap
Hash 기반 · 분산 저장
접근 (Access)O(1) 인덱스 직접 이동O(1) 해시 계산 후 위치 지정
검색 (Search)O(n) 모든 요소를 순차 탐색O(1) 키값으로 직접 위치 지정
삽입 (Insert)O(n) 중간 삽입 시 원소 이동 필요
※ 끝 삽입 제외
O(1) 빈 슬롯에 직접 삽입
삭제 (Remove)O(n) 삭제 후 빈 자리 채우기 이동O(1) 해시 슬롯 제거로 완료

컨테이너 TArray, TSet, TMap의 기능들

TArray - 템플릿 vector배열

TArray<Type> arr;
 :
TArray 선언

arr.Add(value)
 : value를 임시 메모리공간에 카피 후 삽입 (안정성↑, 속도↓)

 arr.Emplace(value)
 : value를 직접 삽입 (내부에서 즉시 생성하므로 빠르지만 의도치 않은 암시적 변환 발생우려 있음.
 : (안정성↓, 속도↑)

arr.Insert(value, Index)
 : vector.insert()와 동일한 삽입매커니즘.

arr.Num()
 : TArray의 크기를 반환한다. vectec.size()와 같은 매커니즘.

arr.Sort(Pred)
 : TArray를 정렬한다. 아래처럼 기본 오름차순, 함수객체를 삽입할 수도 있으며, 람다함수를 사용하는 것이 제일 간편하다.

[](){} : 람다함수
[ ] 캡쳐블록: =(외부에서 복사해서 가져오기), &(외부에서 참조해서 가져오기),
this(이 클래스에서만 가져오기)

arr.FilterByPredicate(Pred)
 : 조건(Pred)에 맞는 원소만을 가진 TArray를 반환한다.

arr.Find(value)
 : 해당 값을 가진 인덱스를 반환한다.

arr.Contains(value)
 : 해당 값의 존재여부를 반환한다.

arr.Remove(value)
 : 해당 값을 가진 모든 원소를 제거한다.

arr.RemoveSingle(value)
 : 해당 값을 가진 첫번째 원소를 제거한다.

arr.RemoveAt(Index)
 : 해당 인덱스의 원소를 제거한다. (주의: 존재하지 않는 인덱스 삭제 시 크래시발생)
 : 밑의 IsValidIndex()함수와 함께 사용하는 것을 추천.
 : 두번째 매개변수(AllowShrinking) - 제거 후 빈 공간을 메울 것인가?

arr.IsValidIndex(Index)
 : 해당 인덱스의 존재여부를 반환

arr.RemoveAll(Pred)
 : 조건(Pred)에 부합하는 모든 원소를 제거.

arr.Empty()
 : TArray를 비운다. vector의 empty()하고는 다름에 유의


TSet - 템플릿 set, 중복이 안되는 컨테이너

TSet<Typeset;
 :
 TSet 선언
 : set = { 원소목록 } 으로 선언 즉시 할당이 가능하다.

set.Contains(value)
 : 해당 값의 존재여부를 반환한다.

set.Find(value)
 : 해당 값의 존재위치를 포인터 형태로 반환한다.
 : 포인터를 반환하므로 Type* 변수로 해당 주소를 받아야 한다.

TSet<Type>::TIterator It = set.CreateIterator();
 : TSet에서 사용하는 반복자(Iterator). 아래의 반복자 함수와 같이 사용한다.
 : 반복자 생성 시 set.CreateIterator()로 할당한다.

TSet<Type>::TConstIterator It = set.CreateConstIterator();
 : TSet 반복자의 const버전.
 : 반복자 생성 시 set.CreateConstIterator()로 할당한다.

It.RemoveCurrent()
 : 반복자가 가리키는 칸을 삭제한다.

TIterator를 사용한 반복자순회반복자함수

setA.Union(setB)
 : 두 Set의 합집합인 Set을 반환한다.

setA.Intersect(setB)
 : 두 Set의 교집합Set을 반환한다.

set.Compact()
 : 제거나 Reserve등으로 발생빈 공간(Slack)을 뒤로 보낸다.
 : 보통 아래 Shrink()와 같이 쓴다.

set.Shrink()
 : 빈 공간(Slack)을 뒤에서부터 지운다. 중간에 있는 Slack은 지우지 않는다.
 : 보통 Compact()이후에 사용한다.

※ 빈 공간(Slack)이 생기는 이유 : 반복자 순회 중 제거가 발생해도 칸이 밀리지 않게 하기 위해 남김.

set.Array()
 : 해당 Set을 TArray로 변환해서 반환한다.


TMap - 템플릿 Map, 키와 값이 있는 컨테이너

TMap<Type, Type> map;
 : TMap 선언

map.Add(key, value)
map.Emplace(key, value)

 : 키-값 추가. 중복된 키의 값은 덮어씌운다.

map.FindOrAdd(key)
 : 해당 키가 존재하면 값을 반환하고, 그렇지 않으면 만들어서 반환한다.
 : 반환 시 참조타입(&)으로 반환한다.

map[key]
 : 해당 키값을 참조. (주의: 키가 존재하지 않으면 크래시 발생, 위험한 코드)

map.Contains(key)
 : 해당 키존재여부를 반환한다.

map.Find(key)
 : 해당 키에 해당하는 값을 포인터(*)로 반환한다.

for(TPair<key,value>& m : map)
 : TPair를 사용해 map을 순회
 : 이것보다는 반복자 순회가 더 편할 수 있음.

TMap<TypeType>::TIterator It = map.CreateIterator();
TMap<Type, Type>::TConstIterator It = map.CreateConstIterator();
 : TMap에서 사용하는 반복자(Iterator). TSet의 TIterator와 사용방법 동일.
 : 아래의 반복자 함수를 사용할 수 있다.

It.Key()
 : 반복자가 가리키는 Key를 반환한다.

It.RemoveCurrent()
 : 반복자가 가리키는 칸을 삭제한다.

map.Remove(key)
 : 해당 키가 가리키는 값을 모두 삭제한다.

map.Compact()
 : 제거나 Reserve등으로 발생한 빈 공간(Slack)을 뒤로 보낸다.
 : 보통 아래 Shrink()와 같이 쓴다.

map.Shrink()
 : 빈 공간(Slack)을 뒤에서부터 지운다. 중간에 있는 Slack은 지우지 않는다.
 : 보통 Compact()이후에 사용한다.

댓글

이 블로그의 인기 게시물

내일배움캠프 사전캠프 - 사전캠프설 연휴 커피 파밍 이벤트 작품 [ EXTREMITY ]

내일배움캠프 29일차 - 커리어데이 2일차 : 클라이언트 프로그래머로서 포트폴리오, 입사준비팁