Unreal5/Unreal C++

[U C++] UCL - TArray

taene_ 2024. 7. 9. 22:45

TArray 개요

  • https://dev.epicgames.com/documentation/ko-kr/unreal-engine/array-containers-in-unreal-engine
  • TArray는 가변 배열(Dynamic Array) 자료구조
  • C++ STL의 vector와 동작 원리가 유사하다.
  • 게임 제작에선 가변 배열 자료구조를 효과적으로 활용하는 것이 좋다.
    • 데이터가 순차적으로 모여있기 때문에 메모리를 효과적으로 사용할 수 있고 캐시 효율이 높다.
    • 컴퓨터 사양이 좋아지면서, 캐시 지역성(Locality)으로 인한 성능 향상이 굉장히 중요해졌다.
    • 임의 데이터의 접근이 빠르고, 고속으로 요소를 순회하는 것이 가능하다.
  • 가변 배열의 단점
    • 맨 끝에 데이터를 추가하는 것은 가볍지만, 중간에 요소를 추가하거나 삭제하는 작업은 비용이 크다.
  • 데이터가 많아질 수록 검색, 삭제, 수정 작업이 느려지기 때문에, 많은 수의 데이터에서 검색 작업이 빈번하게 일어난다면 TArray대신 TSet을 사용하는 것이 좋다.
  • TArray는 값 유형으로, 동적 할당을 하지 않는다.
TArray<int32> IntArray;
IntArray.Init(10, 5);	// IntArray == [10,10,10,10,10]
// Init(value, array_size) 

TArray<FString> StrArr;
StrArr.Add(TEXT("Hello"));
StrArr.Emplace(TEXT("World"));	// StrArr == ["Hello","World"]
// Add() - 추가할 데이터를 밖에서 생성하 후 TArray에 복사해서 집어넣는 방식
// Emplace() - TArray 자체에서 바로 생성(Add보다 더 효율적임)

FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));	//StrArr == ["Hello","World","of","Tomorrow"]
// Append() - 배열 포인터 및 해당 배열의 크기에 다수의 엘리먼트를 한꺼번에 추가

StrArr.AddUnique(TEXT("!"));	// StrArr == ["Hello","World","of","Tomorrow","!"]
StrArr.AddUnique(TEXT("!"));	// StrArr is unchanged as "!" is already an element
// AddUnique() - 동일한 엘리먼트가 존재하는지 검사(존재하지 않을 경우 새 엘리먼트만 추가)

StrArr.Insert(TEXT("Brave"), 1);	// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]
// Insert() - 엘리멘트를 주어진 인덱스에 추가(전체적인 메모리 구조가 바뀌므로 비용이 발생함)

StrArr.SetNum(8);	// StrArr == ["Hello","Brave","World","of","Tomorrow","!","",""]
// SetNum() - 배열의 크기를 인위적으로 늘렸다가 줄였다가 가능

FString JoinedStr;
for (auto& Str : StrArr)
{
    JoinedStr += Str;
    JoinedStr += TEXT(" ");
}
// JoinedStr == "Hello Brave World of Tomorrow ! "

for (int32 Index = 0; Index != StrArr.Num(); ++Index)
{
    JoinedStr += StrArr[Index];
    JoinedStr += TEXT(" ");
}

StrArr.Sort();	// StrArr == ["!","Brave","Hello","of","Tomorrow","World"]
// Sort() - 정렬 후 기존의 순서를 유지하지 않는 불안정한 정렬

// StableSort() - 정렬 후 기존의 순서를 유지하는 안정한 정렬
// Num() - 배열의 요소의 개수 확인 
// GetData() - 배열 내 요소에 대한 포인터를 반환
// GetTypeSize() - 배열의 엘리먼트사이즈(요소크기)를 반환
// IsValidIndex() - 특정 인덱스가 유효한지 true/false로 확인
// 등등 많은 함수를 지원한다.

 

 

TArray의 활용

// .cpp


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

void UMyGameInstance::Init()
{
	Super::Init();

	const int32 ArrayNum = 10;
	TArray<int32> Int32Array;

	for (int32 ix = 1; ix <= ArrayNum; ++ix)
	{
		Int32Array.Add(ix);	// 성능적으론 Emplace()가 좋다.
	}

	Int32Array.RemoveAll(
		[](int32 Val)
		{
			return Val % 2 == 0;	// 짝수만 모두 지우기(RemoveAll()의 조건식은 람다가 들어간다)
		}
	);

	Int32Array += {2, 4, 6, 8, 10};	// 1 3 5 7 9 2 4 6 8 10

	TArray<int32> Int32ArrayCompare;
	int32 CArray[] = { 1,3,5,7,9,2,4,6,8,10 };	// 이 int32 배열을 TArray에 넣어줘야한다.
	Int32ArrayCompare.AddUninitialized(ArrayNum);	
	// AddUninitialized() - 배열에 초기화되지 않은 공간을 추가한다(Memcpy 호출로 전체 구조체를 덮어쓰는 경우처럼 생성자 호출을 피할 때 좋다)
	FMemory::Memcpy(Int32ArrayCompare.GetData(), CArray, sizeof(int32) * ArrayNum);	// 메모리를 통한 Memcpy()로 배열 복사
	ensure(Int32Array == Int32ArrayCompare);	// 통과!

	int32 SumByAlgo = Algo::Accumulate(Int32Array, 0);	// Algo::Accumulate(배열이름, 처음 시작 값)
	ensure(SumByAlgo == 55);	// 통과!
}