다양한 기록

[UE] Unreal Container Library / Struct, Map 본문

언리얼 엔진/Unreal C++

[UE] Unreal Container Library / Struct, Map

라구넹 2024. 12. 28. 19:17

구조체

USTRUCT 매크로 추가하고 (내부에 BlueprintType같은 키워드 추가 가능)

내부 상단에 GENERATED_BODY() 추가 (안해도 되긴 하는데 해야 리플렉션, 직렬화 가능)

내무 멤버 변수는 UPROPERTY() 매크로 사용

* 언리얼 오브젝트가 아니니 F로 시작

 

사용 용도가 언리얼 오브젝트와는 완전히 다름

단순한 데이터 타입에 적합함

F로 시작하니까 일반 객체 -> 리플렉션 시스템이 인식 못함

대신 내부의 UPROPERTY로 선언된 변수들은 인식함

UFUNCTION()은 안됨 

대부분 힙 할당 없이 스택 내 데이터로 사용,

NewObject()도 지원 안됨

Unreal Obejct 계층 구조

하나의 멤버를 UFiled 클래스가 관리

UScriptStruct가 GENERATED_BODY가 들어간 구조체

UFinction은 UClass는 가져도 다른 애들은 안가짐

 

// 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(const FString& InName, const 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;

	UPROPERTY()	// 내부적으로 포인터를 가져서 메모리 관리를 위해 필수 
	TArray<TObjectPtr<class UStudent>> Students;

};

FStudentData로 TArray를 선언 시 -> UPROPERTY 안해줘도 됨

UStudent로 선언 시 -> 해줘야 됨

// 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 ArrayNum = 10;
	TArray<int32> Int32Array;

	for (int32 ix = 1; ix <= ArrayNum; ++ix)
	{
		Int32Array.Add(ix);
	}

	Int32Array.RemoveAll(
		[](int32 Val) 
		{
			return Val % 2 == 0;
		}
	);

	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, ArrayNum * sizeof(int32));

	ensure(Int32Array == Int32ArrayCompare);
	
	int32 Sum = 0;
	for (const auto& num : Int32Array)
	{
		Sum += num;
	}

	int32 SumByAlgo = Algo::Accumulate(Int32Array, 0);

	ensure(Sum == SumByAlgo);

	////////////////////////////////////////////////////////////

	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);

	const int32 StudentNum = 300;
	for (int i = 0; i < StudentNum; ++i)
	{
		StudentsData.Emplace(FStudentData(MakeRandomName(), i));
	}

	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());
}

이름과 순서를 구조체에 담아 TArray에 저장

Transform으로 컨테이너 변경 가능, 이때 람다 함수 사용

 

실행 잘 됨


TMap

STL map과 TMap의 차이

STL map

- 이진 트리로 구성, 메모리 구성 비효율적, 데이터 삭제 시 재구축 가능

- 순회에 적합하진 않음

TMap

- 키, 밸류 구성 튜플 TSet 구조

- 해시 테이블이라 빠른 검색, 동적 배열 등 TSet과 거의 유사한 구조

- TMultiMap 사용 시 중복 데이터(키 기준)를 관리 가능

 

TMap

내부적으로 엘리먼트는 TPair<KeyType, ElementType>으로 구성 

키 유형은 GetTypeHash를 지원해야 하고, == 제공해야 함

 

TMap<int32, FString> FruitMap

이런 식으로 선언 가능

 

TSet과 사용법 거의 동일

 

 

KeyFuncs

커스텀 데이터 자료구조로 만드려면 해시값이 무엇인지 정의 필요

== 이랑 GetTypeHash 선언 필요

 

// 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(const FString& InName, const 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;
};

/**
 * 
 */
UCLASS()
class UNREALCONTAINER_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
	
public:
	virtual void Init() override;
private:
	TArray<FStudentData> StudentsData;

	UPROPERTY()	// 내부적으로 포인터를 가져서 메모리 관리를 위해 필수 
	TArray<TObjectPtr<class UStudent>> Students;

	// 안에 언리얼 오브젝트나 포인터가 들어가진 않아 UPROPERTY 필수는 아님 
	TMap<int32, FString> StudentsMap;
};

FStudentData를 Set에 집어 넣으려고 하면 위에 보이는 것처럼

==이랑 GetTypeHash 선언 필요

 

// 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 ArrayNum = 10;
	TArray<int32> Int32Array;

	for (int32 ix = 1; ix <= ArrayNum; ++ix)
	{
		Int32Array.Add(ix);
	}

	Int32Array.RemoveAll(
		[](int32 Val) 
		{
			return Val % 2 == 0;
		}
	);

	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, ArrayNum * sizeof(int32));

	ensure(Int32Array == Int32ArrayCompare);
	
	int32 Sum = 0;
	for (const auto& num : Int32Array)
	{
		Sum += num;
	}

	int32 SumByAlgo = Algo::Accumulate(Int32Array, 0);

	ensure(Sum == SumByAlgo);

	////////////////////////////////////////////////////////////

	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);

	const int32 StudentNum = 300;
	for (int i = 0; i < StudentNum; ++i)
	{
		StudentsData.Emplace(FStudentData(MakeRandomName(), i));
	}

	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());

	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"), StudentsMapByUniqueName.Num());

	const FString TargetName = TEXT("이혜은");
	TArray<int32> AllOrders;
	StudentsMapByName.MultiFind(TargetName, AllOrders);
	UE_LOG(LogTemp, Log, TEXT("이름에 %s인 학생 수: %d"), *TargetName, AllOrders.Num());

	TSet<FStudentData> StudentsSet;
	for (int32 ix = 1; ix <= ArrayNum; ++ix)
	{
		StudentsSet.Emplace( FStudentData(MakeRandomName(), ix) );
	}
}

사용법은 다 비슷함