0. 강의 내용 요약
- TArray: 빈틈없는 메모리, 가장 높은 접근 성능, 가장 높은 순회성능
- TSet: 빠른 중복 감지
- TMap: 중복 불허, 키 밸류 관리
- TMultiMap: 중복 허용, 키 밸류 관리
TArray | TSet | TMap | TMultiMap | |
접근 | O(1) | O(1) | O(1) | O(1) |
검색 | O(N) | O(1) | O(1) | O(1) |
삽입 | O(N) | O(1) | O(1) | O(1) |
삭제 | O(N) | O(1) | O(1) | O(1) |
더보기
구조체와 언리얼 컨테이너 라이브러리
1. TArray, TSet, TMap 컨테이너 라이브러리 내부 구조와 활용 방법
2. 언리얼 구조체의 선언 방법
3. TSet 과 TMap 에서 언리얼 구조체를 사용하기 위해 필요한 함수의 선언과 구현 방법
1. 실습 코드
더보기
1. 헤더 파일
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"
USTRUCT() // 언리얼 구조체임을 선언
struct FStudentData
{
// 이 경우네는 public 으로 끝나게 됨. 여기서 작성하는 매크로들이 public 으로 끝나기 때문에구조체가 원래 가지고 있는 접근 지시자인 public 그대로 사용
GENERATED_BODY()
// 생성자
FStudentData()
{
Name = TEXT("홍길동");
Order = -1;
}
// 인자를 가진 생성자를 만들어서 자유롭게 사용 가능
// 구조체는 언리얼 오브젝트가 아니기 때문에 newAPI 를 사용해서 생성될 일이 없기 때문!
FStudentData(FString InName, int32 InOrder) : Name(InName), Order(InOrder) {}
// 첫번째로 Operate== 를 추가해준다.
bool operator==(const FStudentData& InOther) const
{
return Order == InOther.Order;
}
// 두번째로 getTypeHash 에 대한 선언
// 이것은 전역함수로 선언할수도 있고, 아니면 friend 키워드를 사용해서 안쪽에 선언하면 더 깔끔해짐
// hash 값은 unit32 로 반환을 해주고 getTypeHash 함수를 만들어주되 인자로는 레퍼런스를 넣어주고
// 리턴 값에 대해서는 우리가 이미 가지고 있는 인티저 오더에 대한 hash 값을 이거에 대한 해시값으로 리턴을 해주는 것으로 이 student 에 대한 해시값을 지정
friend FORCEINLINE uint32 GetTypeHash(const FStudentData& InStudentData)
{
return GetTypeHash(InStudentData.Order);
}
UPROPERTY()
FString Name;
UPROPERTY()
int32 Order;
};
/**
*
*/
UCLASS()
class UNREALCONTAINER_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
virtual void Init() override;
private:
// 이 경우는 값타입이기 때문에 메모리를 관리할 필요가 없다.
// 그래서 Reflection 기능으로 뭔가 조회를 하지 않는다면 굳이 UProperty 를 붙일 필요는 없다.
TArray<FStudentData> StudentsData;
// TArray 헤더에서 언리얼 오브젝트 헤더에서 언리얼 오브젝트 포인터를 선언할 때는 TObjectPtr 로 감싸줘야 함.
UPROPERTY() // 이땐느 포인터를 우리가 관리하게 되는데 이렇게 T-Array 에 내부적으로 포인터를 관리하게 되면 반드시 자동으로 언리얼 엔진이 메모리를 관리할 수 있게 U-Property Macro 를 붙여줘야 됨. 필수!!!
TArray<TObjectPtr<class UStudent>> Students;
// TMap
// 키나 밸류에 언리얼 오브젝트 포인터가 들어가면 UPROPERTY 를 반드시 선언해줘야 하는데 없다면 걍 안해줘도 됨
TMap<int32, FString> StudentsMap;
};
2. cpp 파일
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameInstance.h"
#include "Algo/Accumulate.h" // 합계를 구할 수 있는 Accumulate 함수를 사용할 수 있음
FString MakeRandomName()
{
TCHAR FirstChar[] = TEXT("김이박최");
TCHAR MiddleChar[] = TEXT("상혜지성");
TCHAR LastChar[] = TEXT("수은원연");
TArray<TCHAR> RandArray;
RandArray.SetNum(3); // 일단 기본값으로 3개 채워
RandArray[0] = FirstChar[FMath::RandRange(0, 3)];
RandArray[1] = MiddleChar[FMath::RandRange(0, 3)];
RandArray[2] = LastChar[FMath::RandRange(0, 3)];
// TArray<TCHAR> 는 캐릭터 배열을 그냥 포함하고 있는 컨테이너
// 이 포인터 값을 우리가 넘겨주면 반환 값을 F-String 으로 지정했기 때문에 자동으로 F-String 이 만들어져서 반환됨.
return RandArray.GetData();
}
void UMyGameInstance::Init()
{
Super::Init();
// 간단한 정수형의 TArray 실습
const int32 ArrayNum = 10;
TArray<int32> Int32Array;
for (int32 ix = 1; ix < ArrayNum; ++ix) {
Int32Array.Add(ix); // 성능을 정말 신경쓰고 싶다면 Add 말고 emplace 쓰기
}
// 조건문에 해당하는 걸 람다함수로 넣는 것이 일반적
Int32Array.RemoveAll(
[](int32 Val) {
// 짝수만 다 제거하도록 람다 함수 넣기..
return Val % 2 == 0;
}
);
Int32Array += {2, 4, 6, 8, 10}; // 이제 배열의 모습은 1,3,5,7,9,2,4,6,8,10
// 이번에는 C 스타일처럼 row 하게 바로 메모리에 접근
TArray<int32> Int32ArrayCompare;
int32 CArray[]{ 1,3,5,7,9,2,4,6,8,10 };
Int32ArrayCompare.AddUninitialized(ArrayNum); // Int32ArrayCompare 에 일단 10개 를 넣어줌..
// 복제
FMemory::Memcpy(Int32ArrayCompare.GetData(), CArray, sizeof(int32) * ArrayNum);
// 이제 두개가 같은지 확인
ensure(Int32Array == Int32ArrayCompare); // 이렇게 했을 때 문제 없으면 같은거임
// 더해보장!
int32 Sum = 0;
for (const int32& Int32Elem : Int32Array)
{
Sum += Int32Elem;
}
int32 SumByAlgo = Algo::Accumulate(Int32Array, 0); // 이렇게 하면 쉽게 더할 수 있음
ensure(Sum == SumByAlgo);
// TSet 이용해보자
TSet<int32> Int32Set;
for (int32 ix = 1; ix <= ArrayNum; ++ix)
{
Int32Set.Add(ix);
}
Int32Set.Remove(2);
Int32Set.Remove(4);
Int32Set.Remove(6);
Int32Set.Remove(8);
Int32Set.Remove(10);
Int32Set.Add(2);
Int32Set.Add(4);
Int32Set.Add(6);
Int32Set.Add(8);
Int32Set.Add(10);
// 구조체를 사용해서 300명의 학생을 만들어봥
// 랜덤하게 이름 생성하는 함수
const int32 StudentNum = 300;
for (int32 ix = 1; ix <= StudentNum; ++ix)
{
// 구조체로 데이터를 집어넣는 경우에는 아무래도 복사 비용이 조금 발생하기 때문에 Add 보다는 emplace 사용해주는 것이..
StudentsData.Emplace(FStudentData(MakeRandomName(), ix));
}
// StudentsData 를 TArray 로 옮겨보장
TArray<FString> AllStudentsName;
Algo::Transform(StudentsData, AllStudentsName,
[](const FStudentData& Val) {
return Val.Name;
}
);
UE_LOG(LogTemp, Log, TEXT("모든 학생 이름의 수 : %d"), AllStudentsName.Num());
// StudentsData 를 TSet 으로 옮겨보장
// 얘는 중복을 허용하지 않아용
TSet<FString> AllUniqueNames;
Algo::Transform(StudentsData, AllUniqueNames,
[](const FStudentData& Val)
{
return Val.Name;
}
);
UE_LOG(LogTemp, Log, TEXT("중복 없는 학생 이름의 수 : %d"), AllUniqueNames.Num());
// TMap 으로 옮겨보장
Algo::Transform(StudentsData, StudentsMap,
[](const FStudentData& Val) {
return TPair<int32, FString>(Val.Order, Val.Name);
}
);
UE_LOG(LogTemp, Log, TEXT("순번에 따른 학생 맵의 레코드 수 : %d"), StudentsMap.Num());
// 일반 맵의 경우에는 중복을 허용하지 않기 때ㅐ문에 유니크 네임이라고 이름지음
TMap<FString, int32> StudentsMapByUniqueName;
Algo::Transform(StudentsData, StudentsMapByUniqueName,
[](const FStudentData& Val) {
return TPair<FString, int32>(Val.Name, Val.Order);
}
);
UE_LOG(LogTemp, Log, TEXT("이름에 따른 학생 맵의 레코드 수 : %d"), StudentsMapByUniqueName.Num());
// 근데 만약 중복을 허용하고 싶어
TMultiMap<FString, int32> StudentMapByName;
Algo::Transform(StudentsData, StudentMapByName,
[](const FStudentData& Val) {
return TPair<FString, int32>(Val.Name, Val.Order);
}
);
UE_LOG(LogTemp, Log, TEXT("이름에 따른 학생 멀티맵의 레코드 수 : %d"), StudentMapByName.Num());
// 어떤 이름을 가진 학생이 몇 명있는지 정보 뽑아봦
const FString TargetName(TEXT("이혜은"));
TArray<int32> AllOrders;
StudentMapByName.MultiFind(TargetName, AllOrders);
UE_LOG(LogTemp, Log, TEXT("이름이 %s인 학생 수 : %d"), *TargetName, AllOrders.Num());
// TSet 에 FStudentData 자료구조를 선언해본다
TSet<FStudentData> StudentsSet;
for (int32 ix = 1; ix <= StudentNum; ++ix)
{
// 우리가 지정한 커스텀 구조체에 대한 여기 보이는 것처럼 getTypeHash 함수가 지정되엉 ㅣㅆ지 않아서 hash 값을 만들 수 없다는 에러 메시지 뜸
// 이것을 위해 FStudentData 에 두가지 함수를, 구조체 두가지 함수를 추가해 보도록 한다.
StudentsSet.Emplace(FStudentData(MakeRandomName(), ix));
}
}
2. 공식문서
언리얼 엔진의 맵 컨테이너 | 언리얼 엔진 5.5 문서 | Epic Developer Community