언리얼 컨테이너 라이브러리(2) - 구조체와 Map

myeongrangcoding·2023년 12월 2일

언리얼 구조체 UStruct

  • 데이터 저장, 전송에 특화된 가벼운 객체
  • 대부분 GENERATE_BODY() 매크로를 선언해준다.
    • 리플렉션, 직렬화와 같은 유용한 기능을 지원함.
    • GENERATE_BODY()를 선언한 구조체는 UScriptStruct 클래스로 구현됨.
    • 이 경우 제한적으로 리플렉션을 지원함
      • 속성 UPROPERTY()만 선언, 함수 UFUNCTION()은 선언할 수 없음
  • 구조체 이름은 F로 시작함.
    • 대부분 스택 내 데이터로 사용됨.(NewObject API를 사용할 수 없음.)

MyGameInstance.h

// 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
{
	GENERATED_BODY()

	FStudentData()
	{
		Name = TEXT("홍길동");
		Order = -1;
	}

	FStudentData(FString InName, int32 InOrder) : Name(InName), Order(InOrder) {}

	UPROPERTY()
	FString Name;

	UPROPERTY()
	int32 Order;
};

/**
 * 
 */
UCLASS()
class UNREALCONTAINER_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
	
public:

	virtual void Init() override;

private:
	TArray<FStudentData> StudentsData;

	// 언리얼 오브젝트 헤더에서 언리얼 오브젝트 포인터를 선언할 때는 TObjectPtr로 감싸줘야 된다.
	// TArray에 내부적으로 포인터를 관리하게 되면 반드시 자동으로 언리얼 엔진이 메모리를 관리할 수 있게
	// UPROPERTY() 매크로를 붙여줘야 된다.(필수.)
	UPROPERTY()
	TArray<TObjectPtr<class UStudent>> Students;
};

MyGameInstance.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyGameInstance.h"
#include "Algo/Accumulate.h"

FString MakeRandomName()
{
	TCHAR FirstChar[] = TEXT("김이박최");
	TCHAR MiddleChar[] = TEXT("상혜지성");
	TCHAR LastChar[] = TEXT("수은원연");

	TArray<TCHAR> RandArray;
	RandArray.SetNum(3);
	RandArray[0] = FirstChar[FMath::RandRange(0, 3)];
	RandArray[1] = MiddleChar[FMath::RandRange(0, 3)];
	RandArray[2] = LastChar[FMath::RandRange(0, 3)];

	return RandArray.GetData();
}

void UMyGameInstance::Init()
{
	Super::Init();
	
	const int32 StudentNum = 300;
	for (int32 ix = 1; ix <= StudentNum; ++ix)
	{
		StudentsData.Emplace(FStudentData(MakeRandomName(), ix));
	}

	TArray<FString> AllStudentsNames;
	Algo::Transform(StudentsData, AllStudentsNames,
		[](const FStudentData& Val)
		{
			return Val.Name;
		}
	);

	UE_LOG(LogTemp, Log, TEXT("모든 학생 이름의 수: %d"), AllStudentsNames.Num());

	TSet<FString> AllUniqueNames;
	Algo::Transform(StudentsData, AllUniqueNames,
		[](const FStudentData& Val)
		{
			return Val.Name;
		}
	);

	UE_LOG(LogTemp, Log, TEXT("중복 없는 학생 이름의 수: %d"), AllUniqueNames.Num());
}

TMap

STL map과 TMap의 비교

  • STL map
    • 이진 트리.
    • 정렬.
    • 메모리 구성이 효율적이지 않음.
    • 데이터 삭제시 재구축이 일어날 수 있음.
    • 모든 자료를 순회하는데 적합하진 않음.
  • 언리얼 TMap
    • 키, 밸류 튜플(Tuple) 데이터의 TSet 구조로 구현되어 있음
    • 해시테이블 형태로 구축, 빠른 검색 가능.
    • 동적 배열의 형태로 데이터가 모여있음.
    • 빠르게 순회할 수 있음.
    • 재구축이 일어나지 않음.
    • 비어있는 데이터가 있을 수 있음.
    • TMultiMap: 중복 데이터 관리.
    • 동작 원리는 STL unordered_map과 유사함.

void UMyGameInstance::Init()
{
	Super::Init();
	
	const int32 StudentNum = 300;
	for (int32 ix = 1; ix <= StudentNum; ++ix)
	{
		StudentsData.Emplace(FStudentData(MakeRandomName(), ix));
	}
    
	TMap<int32, FString> StudentsMap;
	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> StudentsMapByName;
	Algo::Transform(StudentsData, StudentsMapByName,
		[](const FStudentData& Val)
		{
			return TPair<FString, int32>(Val.Name, Val.Order);
		}
	);

	UE_LOG(LogTemp, Log, TEXT("이름에 따른 학생 멀티맵의 레코드 수: %d"), StudentsMapByName.Num());

	const FString TargetName(TEXT("이혜은"));
	TArray<int32> AllOrders;
	StudentsMapByName.MultiFind(TargetName, AllOrders);

	UE_LOG(LogTemp, Log, TEXT("이름이 %s인 학생 수: %d"), *TargetName, AllOrders.Num());
}

TSet<FStudent>

struct FStudentData

USTRUCT()
struct FStudentData
{
	GENERATED_BODY()

	FStudentData()
	{
		Name = TEXT("홍길동");
		Order = -1;
	}

	FStudentData(FString InName, int32 InOrder) : Name(InName), Order(InOrder) {}

	bool operator==(const FStudentData& InOther) const
	{
		return Order == InOther.Order;
	}

	friend FORCEINLINE uint32 GetTypeHash(const FStudentData& InStudentData)
	{
		return GetTypeHash(InStudentData.Order);
	}

	UPROPERTY()
	FString Name;

	UPROPERTY()
	int32 Order;
};

TSet<FStudent>

void UMyGameInstance::Init()
{
	Super::Init();
	
	const int32 StudentNum = 300;
	TSet<FStudentData> StudentsSet;
	for (int32 ix = 1; ix <= StudentNum; ++ix)
	{
		StudentsSet.Emplace(FStudentData(MakeRandomName(), ix));
	}
}

시간 복잡도 비교

  • TArray: 빈틈없는 메모리, 가장 높은 접근성능, 가장 높은 순회성능
  • TSet: 빠른 중복 감지
  • TMap: 중복 불퍼, 키, 밸류 관리
  • TMultiMap: 중복 허용, 키, 밸류 관리
TArrayTSetTMapTMultiMap
접근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)
profile
명랑코딩!

0개의 댓글