언리얼의 자료구조 TArray
에대해 알아보겠습니다.
TArray
는 언리얼엔진4 에서 가장 간단한 배열 컨테이너 클래스
TArray
는 유형이 같은 다른오브젝트 들을 순서대로 정리하여 저장하는 클래스이다.
TArray
는 시퀀스 형태로 함수를 이용하여 오브젝트와 순서를 결정지을 수 있다.
TArray
는 언리얼에서 가장 자주쓰이는 컨테이너 클래스로 신속성, 메모리 효율성, 안정성을 염두하여 디자인 되었다.
TArray
는 주로 2가지 프로퍼티로 구분되며 *엘리먼트(element) 유형 과 *할당자(allocator) 입니다.
엘리먼트(element)
- 엘리먼트는 배열에 저장되는 오브젝트 유형
- TArray는 동질성 컨테이너로 저장되는 엘리먼트의 유형이 엄격히 같아야하며 유형이 다른 엘리먼트는 저장할수없다.
할당자(allocator)
- 할당자 는 메모리에 오브젝트가 레이아웃되는 방식, 배열에 엘리먼트를 넣어 배열을 키우는방식을 결정
- 대부분 생략되는 경우가많으며 (기본값은 1) 대부분 적합한 값으로 사용된다.
- 할당자를 다루는 방식은 여러가지가 있으며 기본 작동적합하지 않을경우 직접 작성하여 사용한다.
TArray
는 값 유형은 int32
,float
와 같은 내장형 자료형과 비슷하게 취급해야한다.
TArray
는 확장을 염두해두지 않았기때문에 인스턴스를 new
및 delete
로 생성, 삭제 를하는것은 좋지않다.
TArray
의 엘리먼트는 값유형이기도하며 배열이 소유한다.
TArray
의 소멸은 소속 엘리먼트의 소멸로 이어진다.
TArray
변수를 다른 TArray
엘리먼트로 할당해도 엘리먼트를 복사하여 할당하며 엘리먼트가 공유되지는 않는다.
TArray
에 정수를 담아 사용하기위해 빈배열을 생성.
TArray<int32> IntArray; //자료형<엘리먼트의자료형> 변수이름
int32
, FString
, TSharedPtr
과같은 C++에서 복사 및 소멸이 가능한 유형이라면 어떤것이든 가능하다.TArray
를 채우는 방식은 여러가지가 존재하며 그중 Init
함수는 배열을 엘리먼트의 복사본 여러개로 채우는것이다.
IntArray.Init(10, 5); // IntArray == [10,10,10,10,10] // 10복사본 5개를 할당
Add
와 Emplace
함수를 사용해서 배열 끝에 새 오브젝트를 만들 수 있다.
TArray<FString> StrArr; StrArr.Add (TEXT("Hello")); //Add를 이용하여 Text 타입의 "Hello" 문자열을 배열끝에 할당 StrArr.Emplace(TEXT("World")); //Emplce를 이용하여 Test타입의 "World" 문자열을 배열끝에 할당 // StrArr == ["Hello","World"]
TArray
의 얼로케이터는 엘리먼트가 추가될때마다 필요에따라 메모리를 제공한다.
기본 얼로케이터는 다수의 엘리먼트가 추가될때마다 충분한 양의 메모리를 제공한다.
Add
와 Emplace
의 동작방식은 미묘한 차이가 있다.
Add
(또는 Push
) 는 엘리먼트 유형의 인스턴스를 배열에 복사 (또는 이동)한다.Emplace
는 지정한 인수를 사용하여 엘리먼트 유형의 인스턴스를 새로 생성한다. 즉 TArray<FString>
의 경우
FString
리터널을 생성후 컨테이너안의 새로운FString
으로 이동시킨다.최종 결과는 같지만, Emplace
는 임시 변수 생성을 하지 않는다.
FString
처럼 복잡한 값 유형은 퍼포먼스상 바람직하지 않은 경우가 많기 때문이다.
Emplace
가 Add
보다 좋은점은 불필요한 복사, 이동이 없다는점! Add
를 그외에는 Emplace
를 권장한다.Append
는 다른 TArray
또는 일반 C배열로의 포인터 및 해당 배열의 크기에 다수의 엘리먼트를 한꺼번에 추가한다.
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") }; StrArr.Append(Arr, ARRAY_COUNT(Arr)); // StrArr == ["Hello","World","of","Tomorrow"]
AddUnique
는 기존배열에 동일한 엘리먼트가 존재하지 않는 경우 새 엘리먼트만 추가한다.
operator==
를 사용해서 검사한다.StrArr.AddUnique(TEXT("!")); // StrArr == ["Hello","World","of","Tomorrow","!"] StrArr.AddUnique(TEXT("!")); // StrArr is unchanged as "!" is already an element
Insert
는 Add
, Emplace
, Append
처럼 단일 엘리먼트나 엘리먼트 배열 사본을 지정한 위치에 추가한다.
StrArr.Insert(TEXT("Brave"), 1); // StrArr == ["Hello","Brave","World","of","Tomorrow","!"]
SetNum
함수는 배열의 크기를 직접 설정할 수 있다.
설정된 크기가 현재 배열 크기보다 큰 경우 기본 생성자의 엘리먼트 유형을 사용해서 엘리먼트를 새로 만든다.
StrArr.SetNum(8); // StrArr == ["Hello","Brave","World","of","Tomorrow","!","",""]
SetNum 의 설정크기가 현재 배열 크기보다 작은 경우 엘리먼트를 제거하기도 한다.
StrArr.SetNum(6); // StrArr == ["Hello","Brave","World","of","Tomorrow","!"]
TArray
의 엘리먼트에 대한 반복처리(iterate)를 하는 방법은 여러가지 있으나, C++ 의 범위 for 기능을 사용하는 것을 권장한다.
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(" "); }
배열 반복처리에 대한 보다 세밀한 제어가 가능하다.
CreateIterator
- 읽기,쓰기CreateConstIterator
- 읽기전용for (auto It = StrArr.CreateConstIterator(); It; ++It) { JoinedStr += *It; JoinedStr += TEXT(" "); }
배열은 Sort
함수를 호출하는 것으로 간단히 정렬 가능하다.
StrArr.Sort(); // StrArr == ["!","Brave","Hello","of","Tomorrow","World"] //알파벳 순서로 정렬됨
<
를 사용해서 값을 정렬한다.설정한 기준으로 정렬하는것도 가능하다. ▽
StrArr.Sort([](const FString& A, const FString& B) { return A.Len() < B.Len(); }); // StrArr == ["!","of","Hello","Brave","World","Tomorrow"]
문자열의 길이로 정렬한 결과
"Hello"
, "Brave"
, "World"
의경우 순서가 기존과 다른데, 이는 Sort
는 동등한 엘리먼트의 경우 비안정적이기 때문이다.Sort
는 간단한 정렬을 위해 구현된 기능HeapSort
은 이진 술부(predicate)가 없어도 힙(Heap) 소팅(Sort) 에 사용 가능하다.
Sort
함수에 비할 때 소팅의 효율성에 따라 선택됩니다.Sort
처럼 HeapSort
도 안정적이지 못합니다.HeapSort로 정렬한 결과 ▽
StrArr.HeapSort([](const FString& A, const FString& B) { return A.Len() < B.Len(); }); // StrArr == ["!","of","Hello","Brave","World","Tomorrow"]
StavleSort
는 정렬 이후 동등한 엘리먼트의 순서를 결정하는데 사용된다.
위에서 Sort 나 HeapSort 대신 StableSort 를 사용했다면 ▽
StrArr.StableSort([](const FString& A, const FString& B) { return A.Len() < B.Len(); }); // StrArr == ["!","of","Brave","Hello","World","Tomorrow"]
"Brave"
, "Hello"
, "World"
가 기존 사전식으로 정렬됩니다. StableSort
는 병합 소트로 구현되었다.
Num
함수를 사용해서 배열에 엘리먼트가 몇 개인지 확인할 수 있다.
int32 Count = StrArr.Num(); // Count == 6
C 스타일 API 같은 것과의 상호 정보 교환을 위해 배열 메모리에 직접 접근할 필요가 있는 경우 사용됨
GetData
함수를 사용해서 배열 내 엘리먼트에 대한 포인터를 반환시킬 수 있다.StrPtr
에서의 Num
인덱스만이 역참조가 가능(dereferenceable)하다.FString* StrPtr = StrArr.GetData(); // StrPtr[0] == "!" // StrPtr[1] == "of" // ... // StrPtr[5] == "Tomorrow" // StrPtr[6] - undefined behavior
컨테이너가 const
인 경우, 반환되는 포인터 역시 const
이다.
컨테이너의 엘리먼트 타입의 크기도 알아낼수 있다.
uint32 ElementSize = StrArr.GetTypeSize(); // ElementSize == sizeof(FString)
엘리먼트 값을 얻으려면, operator[]
인덱싱을 사용해 원하는 엘리먼트에 대한 인덱스 값을 입력한다.
FString Elem1 = StrArr[1]; // Elem1 == "of"
operator[]
는 레퍼런스를 반환하므로, 배열이 const
가 아니라는 가정하에 배열 내의 엘리먼트를 수정할 수도있다.
StrArr[3] = StrArr[3].ToUpper(); // StrArr == ["!","of","Brave","HELLO","World","Tomorrow"] // hello를 대문자로 변환
GetData
함수처럼 operator[]
도 배열이 const
인 경우 const
레퍼런스를 반환한다.
유효하지 않은 인덱스, 즉 0
미만이거나 Num()
이상 값을 전해주면, 실행시간 오류가 발생한다.
컨테이너에 특정 인덱스가 유효한지 IsValidIndex
함수를 통해 확인할수 있다.
bool bValidM1 = StrArr.IsValidIndex(-1); // 0미만 bool bValid0 = StrArr.IsValidIndex(0); bool bValid5 = StrArr.IsValidIndex(5); bool bValid6 = StrArr.IsValidIndex(6); // 최대값 Num을넘김 // bValidM1 == false // bValid0 == true // bValid5 == true // bValid6 == false
Last
함수를 사용하여 배열 끝에서부터 역순으로 인덱스를 사용할 수도 있다.
Top
함수는 Last
의 동의어로, 인덱스를 받지 않는다는 점이 다르다.
FString ElemEnd = StrArr.Last(); FString ElemEnd0 = StrArr.Last(0); FString ElemEnd1 = StrArr.Last(1); FString ElemTop = StrArr.Top(); // ElemEnd == "Tomorrow" // ElemEnd0 == "Tomorrow" // ElemEnd1 == "World" // ElemTop == "Tomorrow"
배열에 특정 엘리먼트가 있는지 확인하는데 사용됨
bool bHello = StrArr.Contains(TEXT("Hello")); bool bGoodbye = StrArr.Contains(TEXT("Goodbye")); // bHello == true // bGoodbye == false
ContainsByPredicate
는 배열에 지정된 술부(predicate)와 일치하는 엘리먼트가 있는지 확인할수 있다.
bool bLen5 = StrArr.ContainsByPredicate([](const FString& Str){ return Str.Len() == 5; }); bool bLen6 = StrArr.ContainsByPredicate([](const FString& Str){ return Str.Len() == 6; }); // bLen5 == true // bLen6 == false
Find
함수를 사용하여 엘리먼트를 찾을 수 있다.
int32 Index;
if (StrArr.Find(TEXT("Hello"), Index))
{
// Index == 3
}
Find
함수는 엘리먼트가 중복될경우 첫번째 엘리먼트를 반환한다.
FindLast
함수를 사용하면 마지막 엘리먼트를 반환한다.
int32 IndexLast; if (StrArr.FindLast(TEXT("Hello"), IndexLast)) { // IndexLast == 3, because there aren't any duplicates }
Find
, FindLast
모두 반환값이 bool
로 반환된다.Find
와 FindLast
는 엘리먼트 인덱스를 직접 반환할 수도 있다.
엘리먼트를 찾지 못했으면, 특수 INDEX_NONE
값이 반환된다.
int32 Index2 = StrArr.Find(TEXT("Hello")); int32 IndexLast2 = StrArr.FindLast(TEXT("Hello")); int32 IndexNone = StrArr.Find(TEXT("None")); // Index2 == 3 // IndexLast2 == 3 // IndexNone == INDEX_NONE
IndexOfByKey
함수는 엘리먼트와 다른 타입과 비교가 가능하다.
Find
의 경우 인수를 엘리먼트 유형으로 변환 하는과정을 거친다.IndexOfByKey
의 경우 엘리먼트와 key
를 바로 비교한다.key
를 엘리먼트 형태로 변환할수 없을때에도 사용가능하다.IndexOfByKey
는 operator==(ElementType, KeyType)
가 존재할경우 작동한다.
IndexOfByKey
는 처음 찾은 엘리먼트의 인덱스를 반환한다.
찾은 것이 없으면 INDEX_NONE
을 반환한다.
int32 Index = StrArr.IndexOfByKey(TEXT("Hello")); // Index == 3
IndexOfByPredicate
함수는 설정한 술부로 일치하는 첫 엘리먼트를 반환한다.
찾은 것이 없으면 마찬가지로 특수 INDEX_NONE
값을 반환한다.
int32 Index = StrArr.IndexOfByPredicate([](const FString& Str){ return Str.Contains(TEXT("r")); }); // Index == 2
FindByKey
함수는 인덱스 대신 찾은 엘리먼트의 포인터를 반환한다.
엘리먼트를 임의의 오브젝트와 비교하는식으로 IndexOfByKey
와 비슷하게 동작하나
찾은것이 없을경우 nullptr
을 반환한다.
auto* OfPtr = StrArr.FindByKey(TEXT("of"))); auto* ThePtr = StrArr.FindByKey(TEXT("the"))); // OfPtr == &StrArr[1] // ThePtr == nullptr
마찬가지로 FindByPredicate
역시 IndexOfByPredicate
처럼 사용되지만, 인덱스의 포인터를 반환한다는 점이 다르다.
auto* Len5Ptr = StrArr.FindByPredicate([](const FString& Str){ return Str.Len() == 5; }); auto* Len6Ptr = StrArr.FindByPredicate([](const FString& Str){ return Str.Len() == 6; }); // Len5Ptr == &StrArr[2] // Len6Ptr == nullptr
FilterByPredicate
함수는 설정한 술부의 조건과 일치하는 엘리먼트 배열을 반환한다.
auto Filter = StrArray.FilterByPredicate([](const FString& Str){ return !Str.IsEmpty() && Str[0] < TEXT('M'); });
Remove
함수로 배열에서 엘리먼트를 지울 수 있다.
Remove
함수는 엘리먼트 유형의 operator==
에 따라, 제공한 것과 동일한 것으로 간주되는 엘리먼트를 모두 지운다.TArray<int32> ValArr; int32 Temp[] = { 10, 20, 30, 5, 10, 15, 20, 25, 30 }; ValArr.Append(Temp, ARRAY_COUNT(Temp)); // ValArr == [10,20,30,5,10,15,20,25,30] ValArr.Remove(20); // ValArr == [10,30,5,10,15,25,30]
RemoveSingle
로 배열에서 처음 찾아내는 엘리먼트를 지울수 있다.
ValArr.RemoveSingle(30); // ValArr == [10,5,10,15,25,30]
RemoveAt
함수로 제거할 엘리먼트를 인덱스로 지정할 수 있다.
IsValidIndex
로 인덱스를 한번 검증하고 사용하는것이 좋은데, 배열에 없는 인덱스를 사용할경우 런타임 오류가 발생한다.ValArr.RemoveAt(2); // 인덱스 2 엘리먼트를 제거합니다 // ValArr == [10,5,15,25,30]
ValArr.RemoveAt(99); // 런타임 오류가 발생합니다 // 인덱스 99 에 엘리먼트가 없기 때문입니다
RemoveAll
함수로 설정한 술부와 일치하는 모든 엘리먼트를 제거한다.
예) 3의 배수인 값을 전부 제거
ValArr.RemoveAll([](int32 Val) { return Val % 3 == 0; }); // ValArr == [10,5,25]
제거 프로세스에는 비용이 따른다..
RemoveSwap
, RemoveAtSwap
, RemoveAllSwap
함수를 사용해서 부하를 줄일 수 있다.Swap
을 이용하면 부하가 줄어들지만, 배열의 순서는 보장받을수 없다.TArray<int32> ValArr2; for (int32 i = 0; i != 10; ++i) ValArr2.Add(i % 5); // ValArr2 == [0,1,2,3,4,0,1,2,3,4]
ValArr2.RemoveSwap(2); // ValArr2 == [0,1,4,3,4,0,1,3]
ValArr2.RemoveAtSwap(1); // ValArr2 == [0,3,4,3,4,0,1]
ValArr2.RemoveAllSwap([](int32 Val) { return Val % 3 == 0; }); // ValArr2 == [1,4,4]
Empty
함수는 배열에서 모든 것을 제거한다.
ValArr2.Empty(); // ValArr2 == []
배열은 일반적인 생성자 복사나 할당 연산자를 통해 복사할 수 있다.
배열은 엘리먼트를 엄격히 소유하기에, 새 배열에는 자체적인 엘리먼트 사본이 생긴다..
TArray<int32> ValArr3; ValArr3.Add(1); ValArr3.Add(2); ValArr3.Add(3);
auto ValArr4 = ValArr3; // ValArr4 == [1,2,3]; ValArr4[0] = 5; // ValArr3 == [1,2,3]; // ValArr4 == [5,2,3];
Append
함수의 대안으로 , operatoe+=
를 통해 배열을 추가할수 있다.
ValArr4 += ValArr3; // ValArr4 == [5,2,3,1,2,3]
MoveTemp
함수를 이용하여 배열을 이동시킬수 있다. 이동 이후 원본 배열은 공백으로 남는다.
ValArr3 = MoveTemp(ValArr4); // ValArr3 == [5,2,3,1,2,3] // ValArr4 == []
배열은 operator==
나 operator!=
를 사용해서 비교할 수 있다.
operator==
를 사용해서 비교한다.TArray<FString> FlavorArr1; FlavorArr1.Emplace(TEXT("Chocolate")); FlavorArr1.Emplace(TEXT("Vanilla")); // FlavorArr1 == ["Chocolate","Vanilla"]
auto FlavorArr2 = Str1Array; // FlavorArr2 == ["Chocolate","Vanilla"]
bool bComparison1 = FlavorArr1 == FlavorArr2; // bComparison1 == true
for (auto& Str : FlavorArr2) { Str = Str.ToUpper(); } // FlavorArr2 == ["CHOCOLATE","VANILLA"]
bool bComparison2 = FlavorArr1 == FlavorArr2; // bComparison2 == true, because FString comparison ignores case
Exchange(FlavorArr2[0], FlavorArr2[1]); // FlavorArr2 == ["VANILLA","CHOCOLATE"]
bool bComparison3 = FlavorArr1 == FlavorArr2; // bComparison3 == false, because the order has changed
출처
언리얼 공식 홈페이지
언리얼 TArray
가 가지고있는 여러 함수들을 알아보았습니다
바로바로 이해가가지않는 부분은 제나름데로 수정을하면서 공부해보았습니다.
수정할부분이 있다면 댓글로 알려주시면 감사합니다.