내일배움캠프 39일차 - 언리얼 엔진 C++ 심화 2 : 컨테이너와 포인터
TObjectPtr
- TObjectPtr은 UE5에서 도입된 템플릿 스마트 포인터이다.
- UE4의 원시포인터 UObject*를 대체하기 위해 사용되었다.
에디터 환경 전용으로 설계되어 두 가지 핵심 기능을 제공한다.
1. 지연로딩: 변수가 실제로 접근될 때만 리소스가 로드되어 시작 시 메모리 과부하를 방지
2. 액세스 트래킹: UPROPERTY변수가 어디서 참조되고 있는지 정밀하게 식별, 지원한다.
TObjectPtr 사용 규칙과 성능
TObjectPtr 권장 사용: 헤더 파일의 UPROPERTY 변수
: UE5에서 권장하는 표준방식으로, 지연 로딩과 액세스 트래킹을 완전히 지원한다.
ex)TObjectPtr<UObject> ptr;
※ TObjectPtr을 사용해도 게임성능은 향상되지 않습니다.
: 패키징(출시) 배포 후의 TObjectPtr은 원시포인터로 자동변환된다.
원시 포인터 사용: 지역 변수, 함수 매개변수
: 이 경우 참조 대상 객체가 이미 메모리에 존재하므로 로딩 로직이 필요없어 원시 포인터가 더 직접적이고 효율적이다.
ex) UObject* ptr;
언리얼 컨테이너
TObjectPtr 컨테이너 순회방법
TObjectPtr 컨테이너를 순회할 때 반복자 타입 선택은 중요하다.
auto* 로 순회: 비권장. 매 반복마다 내부 로드 검사를 실행하여 성능저하가 발생한다.
| auto* 순회는 비권장. |
auto& 로 순회: 권장. 컨테이너 내 TObjectPtr 자체를 직접 참조하여 내부 로드, 주소 계산 과정을 생략하여 최적의 성능을 발휘한다.
| auto& 순회를 권장 |
TSubclassOf - 클래스 레퍼런스 컨테이너
여기 편집 가능한 세 타입변수가 있다. 내가 여기서 'AMyDebugTestActor' 라는 액터클래스를 에디터에서 참조하고싶다 가정하자. 이 중에서 어떤 타입를 쓰는 것이 적합한지 알아보자.
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<Type> set;
: 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<Type, Type>::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()이후에 사용한다.
댓글
댓글 쓰기