메모내용

TArray

TArray 개요

TArray 내부구조

같은 규격을 가진 데이터를 차곡차곡 빈틈없이 이어져 있다

Insert, Remove 함수는 비용이 많이 든다. (메모리 사이즈를 다시 지정해 주어야 하기 때문에)
[ ] 연산자는 한번에 바로 접근이 가능하다.

TArray 유형은 두가지 프로퍼티로 정의 됩니다. (Element Type, Allocator) Eelement Type 은 배열에 저장되는 오브젝트 유형입니다. TArray 는 소위 동질성 컨테이너로, Element 모두 같은 Type이어야 합니다. Allocator 는 꽤 자주 생략되지만 기본값은 1 입니다. 메모리에 오브젝트가 레이아웃 되는 방식과 배열에 Element 를 추가하기 위해 배열을 키울 방식을 정의합니다. TArray 는 값 유형으로, int32 나 float 와 같은 다른 내장형과 비슷하게 취급해야 합니다. 확장을 염두해 두지 않았기에, TArray 인스턴스를 new 및 delete 로 생성 또는 소멸 시키는 것은 좋지 않습니다. 엘리먼트는 값 유형이기도 해서 배열을 소유합니다. TArray 의 소멸은 곧 거기 들어있는 앨리먼트의 소멸로 이어집니다. TArray 변수를 이용해 새로운 TArray 변수를 만들면, 그 앨리먼트를 새 변수에 복사하며, 공유된 상태는 없습니다.

사용법

  • 참조 : Unreal Document TArray
  • 선언

    Element Type 은 int32, FString, TSharedPtr 등과 같이 보통의 C++ 값 규칙에 따라 복사 및 소멸 가능한 값 유형은 어떤것이든 가능합니다. Allocator 가 지정되지 않았으니, TArray 는 기본 힙 기반 Allocator 를 사용합니다. 이 시점에서는 아직 할당된 메모리가 없습니다.

                        
                            TArray<int32> IntArray;
                        
                    
    선언하면서 바로 생성해서 초기화 할 수도 있다.
                        
        
                            TArray<UObject*> ObjectArr = {NewObject<???>(), NewObject<???>(), NewObject<???>()};
        
                            // 내부 순회
                            for( const auto ObjectElem : ObjectArr)
                            {
                                
                                // ...
                            }
        
                        
                    

    배열 채우기

                        
                            // 한번에 채우기
                            InitArray.Init(10,5) // {10,10,10,10,10}
    
                            // 배열 끝에 채우기
    
                            TArray<FString> StrArr;
                            StrArr.Add(TEXT("Hello"));          // 엘리먼트유형의 인스턴스를 (TArry 밖에서 선언된 엘리먼트) 배열에 복사(또는 이동)합니다
                            StrArr.Emplace(TEXT("World"));      // 지정한 인수를 사용하여, Element Type 과 동일한 인스턴스를 TArray 내부에서 생성합니다. 좀더 효율적이다. 복사하는 불필요한 절차를 피할수 있다.
                            // Emplace 가 Add 보다 효율이 더 좋지만, 가독성은 Add 가 좋다.
                            // For 문의 경우에서 int 같이 primitive 유형은 사소하지만 크기가 큰 구조체 같은경우 Emplace 를 고려하는것이 권장됩니다.
                            
    
                            // 다수의 Element 한번에 추가
                            FString Arr[] = {TEXT("of"), TEXT("Tomorrow")};
                            StrArr.Append(Arr, ARRAY_COUNT(Arr));
                            // StrArr == {"Hello", "World", "of", "Tomorrow"};
    
                            // 검색 후 추가하기 ( 새로운 앨리먼트만 추가하는 경우 ) // 이경우 TSet 이 좋다.
                            StrArr.AddUnique(TEXT("!"));
                            // StrArr == {"Hello", "World", "of", "Tomorrow", "!"};
    
                            StrArr.AddUnique(TEXT("!"));
                            // StrArr == {"Hello", "World", "of", "Tomorrow", "!"};  // 이미 있는 값이라 추가되지 않는다
    
                            // 삽입. 단일 엘리먼트나 배열사본을, 주어진 인덱스에 추가시킵니다.
                            StrArr.Insert(TEXT("Brave"),1); // 1번 인덱스에 삽입
                            // StrArr == {"Hello", "Brave", "World", "of", "Tomorrow", "!"};
    
                            // 배열 크기 설정하기. SetNum 으로 설정한 크기가 , 현재 배열크기보다 크게 설정한다면, 기본생성자의 Element Type 으로 새로 만듭니다.
                            StrArr.SetNum(8);
                            // StrArr == {"Hello", "Brave", "World", "of", "Tomorrow", "!","",""};
    
                            // 배열 크기 설정하기. SetNum 으로 설정한 크기가 , 현재 배열크기보다 작게 설정한다면, 설정한 크기만큼 요소를 삭제합니다.
                            Str.SetNum(6);
                            // StrArr == {"Hello", "Brave", "World", "of", "Tomorrow", "!"};
                            
                        
                    
                        
                            // 그외 할당함수들
    
                            /*
                                AddUninitialized
    
                                초기화되지 않은 상태로 요소를 추가할 때 사용합니다.
                                이 방법은 성능상 이점이 있지만, 추가된 요소는 초기화 되지 않으므로 적절히 초기화 해줘야 합니다.
                            */
    
                            TArray<uin8> MyArray;
                            int32 NumToAdd = 50; // 추가할 요소의 수
                            MyArray.AddUninitialized(NumToAdd);
    
                            // 필요시 요소를 초기화
                            for(int32 i = 0; i < NumToAdd ; ++i)
                            {
                                MyArray[i] = 0; // 예시 : 모든 요소를 0으로 초기화
                            }
    
    
                            /*
                                Reserve
    
                                배열의 용량을 미리 할당하여 여러번의 메모리 할당을 피할 수 있습니다.
                                배열의 크기는 변하지 않지만, 메모리를 미리 확보할 수 있습니다.
                            */
                            Tarray<uint8> MyArray;
                            int32 ExpectedSize = 200; // 예상되는 최대 크기
                            MyArray.Reserve(ExpectedSize);
                        
                    

    그외 다른 함수도 있습니다.

    AddUninitialized

    반복처리

    여러가지 방법이 있으나 c++ 의 범위기반 for 기능을 사용하는것을 추천합니다.

                    
                        FString JoinedStr;
    
                        // 범위기반 for
                        for(auto& Str : StrArr)
                        {
                            JoinedStr += Str;
                            JoinedStr += Text(" ");
                        }
                        // JoinedStr == "Hello Brave World of Tomorrow ! ";
    
                        // 인덱스 기반 for
                        for(int32 Index = 0; Index != StrArr.Num(); ++Index)
                        {
                            JoinedStr += StrArr[Index];
                            JoinedStr += TEXT(" ");
                        }
    
                        // Iterator 를 사용한 For
                        // 세밀한 제어가 가능하다
                        // CreateIterator() 를 사용하면 일기 쓰기 가능
                        // CreateConstIterator() 를 사용하면 읽기 전용
                        for (auto Iter = StrArr.CreateConstIterator(); Iter; ++Iter)
                        {
                            JoinedStr += Iter;
                            JoinedStr += TEXT(" ");
                        }
    
    
                    
                

    Query

    operator [] : 직접 접근

    operator[] 을 통해 입력한 인덱스에 해당하는 배열 Element 에 바로 접근할 수 있습니다. 또한 operator 는 레퍼런스를 반환 하므로, 배열이 const 가 아니라는 가정하에 배열 내 Element 를 변형시키는데 사용할 수도 있습니다.

                        
                            FString Elem1 = StrArr[1];
                            // Elem1 == "of";
    
                            
                            StrArr[3] == StrArr[3].ToUpper();
                            // StrArr == ["!","of","Brave","HELLO","World","Tomorrow"];
                        
                    

    IsValidIndex : 컨테이너에 특정 인덱스가 유효한지

    IsValidIndex() 함수를 사용하여 특정 인덱스에 대한 값이 유효한지 확인할 수 있습니다. 이는 유효하지 않은 인덱스에 접근하여 런타임중 에러가 발생하는것을 막아줍니다.

                        
                            StrArr.IsValidIndex(-1);    // false;
                            StrArr.IsValidIndex(0);     // true;
                            StrArr.IsValidIndex(5);     // true;
                            StrArr.IsValidIndex(6);     // false;
                        
                    

    Num : Element 개수 파악

    Num 함수를 사용해서 배열에 몇개의 Element 가 있는지 확인할 수 있습니다.

                        
                            int32 Count = StrArr.Num();
                        
                    

    GetData : Element 포인터 반환

    배열 메모리에 대한 직접접근을 위해, GetData 함수를 사용할 수 있습니다. 배열 내 Element 에 대한 포인터를 반환합니다. (배열명 은 배열의 주소를 가지고 있으니, 배열명 이라고도 볼 수 있겟다.) 이 포인터는 배열에 대한 변형 연상이 적용되기 전에만 유효 합니다. 컨테이너(여기선 TArray)가 const 인 경우, 반환되는 포인터 역시 const 입니다.

                        
                            int32 Int32Arr[];
                            int32* Int32ArrPointer;
                            Int32Arr == Int32ArrPointer == TArray<int32>.GetData();
                        
                    
                        
                            FString* StrPtr = StrArr.GetData();
                            // StrPtr[0] == "!";
                            // StrPtr[1] == "of";
                            // ...
                            // StrPtr[5] == "Tomorrow";
                            // StrPtr[6] //  undefined behavior
                        
                    

    Top , Last : 거꾸로 배열의 끝에서부터 접근하기

                        
                            FString ElemEnd     = StrArr.Last();    // "Tomorrow"
                            FString ElemEnd0    = StrArr.Last(0);   // "Tomorrow"
                            FString ElemEnd1    = StrArr.Last(1);   // "World"
                            FString ElemTop     = StrArr.Top();     // "Tomorrow"
                        
                    

    Remove

    배열요소 제거하는 방법도 여러가지가 있습니다.

    RemoveAll( 조건 ) : 조건(람다식)에 해당하는 모든 요소를 제거

                        
                            TArray<int32> IntArr;
    
                            for(int32 i = 0 ; i <= 10 ; i++)
                            {
                                IntArr.Add(i); // {0,1,2,3,4,5,6,7,8,9,10} 이 들어갈 것이다.
                            }
    
                            // 조건에 맞는 요소를 모두 제거합니다.
                            IntArr.RemoveAll([](int32 Elem){ 
                                    return Elem % 2 == 0; // 즉 짝수만 (true) 제거한다.
                                }
                            )
                        
                    

    Sort 정렬

    여러가지 정렬방식이 있지만, 사용하는 알고리즘에 따라 사용하는 정렬방식이 나뉩니다.

    Sort ( 일반 정렬)

    Element Type 이 가지고 있는 연산자 < 를 사용해서 정렬합니다.
    FString 의 경우 대소문자 구분없이 사전식 비교를 합니다.

                    
                        // 일반정렬 . Element Type 이 가진 < 연산자를 사용하여 비교합니다.
                        StrArr.Sort();
                        // StrArr == {"!", "Brave", "Hello", "of", "Tomorrow", "World"}
    
                        // 일반정렬. 연산방법을 지정해 줄 수도 있습니다. 람다함수(익명함수) 사용
                        // StrArr.Sort([](){})
                        StrArr.Sort([](const FString& A, const FString& B){
                            return A.Len() < B.Len();        // 길이를 비교하여 참 거짓을 반환하도록, true 를 반환한다면 순서를 그대로, false 라면 순서를 바꿉니다.
                        })
                    
                

    HeapSort (힙 정렬)

                    
                        // 힙정렬 . 람다표현식을 제공해도 되고 안해도 되고, 사용법은 Sort 와 같다.
                        StrArr.HeapSort([](const FString& A, const FString& B){
                            return A.Len() < B.Len();
                        })
                        // StrArr == {"!", "Brave", "Hello", "of", "Tomorrow", "World"}
                    
                

    StableSort (병합 정렬)

                    
                        // 병합정렬 . 람다표현식을 제공해도 되고 안해도 되고, 사용법은 Sort 와 같다.
                        StrArr.StableSort([](const FString& A, const FString& B){
                            return A.Len() < B.Len();
                        })
                        // StrArr == {"!", "Brave", "Hello", "of", "Tomorrow", "World"}
                    
                

    배열의 사용 예시

                    
                        TArray<int32> Int32ArrCompare;
                        int32 CArray[] = {1,3,5,7,9,2,4,6,8,10};
                        
                        // AddUninitialized 기존요소를 수정하지 않고 배열 끝에 초기화 되지 않은 Element 를 생성하는 함수
                        // 나중에 구성할 개체에 대한 메모리를 할당하는데 사용할 수 있다.
                        Int32ArrCompare.AddUninitialized(10)
                        
                        // TArray 가 관리하고 있는 배열에 직접 접근하여, Memcpy 를 통해 배열의 정보 빠르게 복사
                        FMemory::Memcpy(Int32ArrCompare.GetData(), CArray, sizeof(int32)*ArrayNum);
                    
                

    알고리즘 라이브러리 사용해보기

    알고리즘 라이브러리를 사용하여 합계 구하기

                    
                        #include "Algo/Accumulate.h"
    
                        {
                            // 일반적으로는 for 구문으로 구한다
                            
                            TArray<int32> IntArr = {1,2,3,4,5,6,7,8,9,10};
    
                            int32 Sum = 0;
                            for(const Int& Int32Elem : Int32Arr)
                            {
                                Sum += Int32Elem;
                            }
    
                            ensure(Sum == 55); // 맞는지 체크
                            
                            // 하지만 알고리즘 라이브러리를 사용해서 구할수도 있다.
    
                            int32 SumByAlgo = Algo::Accumulate(Int32Arr, 0); // Accumulate 함수는 합을 구하는 알고리즘. 인수로 배열과 초기값을 넣어준다.
                            ensure(Sum == SumByAlgo); // 둘다 55 로 같다.
                        }