언리얼 C++ 프로그래밍

[언리얼 C++] 컨테이너 클래스 TArray 사용법 (생성 및 삽입, 반복처리)

언린이 2021. 7. 6. 00:26

언리얼 엔진의 가장 간단한 컨테이너 클래스는 TArray입니다. TArray는 템플릿 클래스로 타입이 같은 다른 오브젝트들을 담을 수 있는 클래스입니다.

 

TArray 컨테이너는 언리얼 엔진에서 가장 자주 쓰이는 컨테이너 클래스로 신속성, 메모리 효율성, 안전성을 염두에 두고 디자인되었습니다. TArray 타입은 두가지 프로퍼티로 정의되는데, 바로 엘리먼트 타입과 얼로케이터입니다.

이 글에서는 주로 사용되는 엘리먼트 타입의 TArray에 대해 다뤄보도록 하겠습니다.

 

엘리먼트 타입은 배열에 저장되는 오브젝트 타입입니다. TArray 컨테이너는 소위 동질성 컨테이너로, 속하는 엘리먼트들 전부 같은 타입이어야 합니다. 타입이 다른 엘리먼트를 하나의 TArray 컨테이너에 저장할 수는 없습니다.

 

TArray 컨테이너는 값의 타입으로, int32나 float 같은 다른 내장형과 비슷하게 취급해야 합니다. 확장을 염두에 두지는 않았기에, TArray 인스턴스를 new와 delete로 생성 또는 소멸시키는 것은 좋지 않습니다. 엘리먼트는 값의 타입이기도 하며 배열이 소유합니다. TArray 컨테이너의 소멸은 곧 컨테이너에 속해있는 엘리먼트들의 소멸로 이어집니다.

 

 

1. TArray 컨테이너 생성 및 채우기

TArray<int32> IntArray;

위 예제 코드는 int32 타입의 엘리먼트들을 저장하도록 디자인된 TArray 컨테이너 객체를 생성한 것입니다.

엘리먼트 타입으로는 int32 뿐만아니라, FString, TSharedPtr 등과 같이 보통의 C++ 규칙에 따라 복사 및 소멸이 가능한 타입은 어떤 것이든 가능합니다.

 

TArray 컨테이너 객체를 엘리먼트들로 채우는 방법은 여러가지가 존재합니다.

IntArray.Init(10, 5); // IntArray = {10,10,10,10,10}

그 중 한가지는 Init 함수입니다. 해당 함수를 사용하여 컨테이너를 엘리먼트 사본 여러개로 채울 수 있습니다.

위 예제 코드에서는 int32 타입의 값 10, 5개를 컨테이너에 채워넣었습니다.

 

TArray<FString> StrArray;
StrArray.Add(TEXT("Hello"));  // StrArray = {"Hello"}
StrArray.Emplace(TEXT("World")); // StrArray = {"Hello","World"}

그리고 Add 함수와 Emplace 함수가 존재하는데, 해당 함수들을 사용하여 컨테이너 끝에 새 오브젝트를 넣을 수 있습니다. Add 함수와 Emplace 함수는 동일한 기능을 수행하지만 미묘한 차이점이 있습니다.

  • Add 함수는 엘리먼트 타입의 인스턴스를 컨테이너에 복사합니다.
  • Emplace 함수는 지정한 파라미터를 사용하여 엘리먼트 타입의 인스턴스를 새로 생성합니다.

즉, 위 예제의 TArray<FString> 컨테이너의 경우, Add 함수는 스트링 리터럴에서 임시 FString을 생성한 다음 그 임시 내용물을 컨테이너 안의 새로운 FString으로 이동시킵니다. 반면에 Emplace 함수는 스트링 리터럴을 사용하여 FString을 직접 만듭니다. 최종 결과는 같지만, Emplace 함수는 임시 변수를 생성하지 않습니다. FString처럼 복잡한 값 타입은 퍼포먼스상 바람직하지 않은 경우가 많기 때문입니다.

일반적으로 Emplace 함수가 Add 함수보다 좋은 점은 호출되는 곳에 임시 생성 후 컨테이너에 복사하는 불필요한 절차를 피할 수 있기 때문입니다. 그렇기 때문에 일반적으로, 단순한 타입에는 Add 함수를, 복잡한 타입에는 Emplace 함수를 사용하시면 됩니다. 물론 효율적인 면에서는 Emplace 함수만을 사용하는 것이 좋지만, Add 함수를 사용하면 가독성을 높일 수 있습니다.

 

FString Arr[] = {TEXT("of"), TEXT("Tomorrow")};
StrArray.Append(Arr, ARRAY_COUNT(Arr)); // StrArray = {"Hello","World","of","Tomorrow"}

Append 함수는 다른 TArray 또는 일반 C 배열로의 포인터 및 해당 배열의 크기에 다수의 엘리먼트를 한꺼번에 넣을 수 있습니다.

 

StrArray.AddUnique(TEXT("!")); // StrArray = {"Hello","World","of","Tomorrow","!"}

StrArray.AddUnique(TEXT("!")); // StrArray is unchanged as "!" is already an element

그리고 AddUnique 라는 함수가 존재하는데, 해당 함수는 기존 컨테이너에 동일한 엘리먼트가 존재하지 않는 경우에만 새 엘리먼트를 추가합니다.

 

StrArray.Insert(TEXT("Brave"), 1); // StrArray = {"Hello","Brave","World","of","Tomorrow","!"}

또한, Insert 함수라는 것이 있는데, 이 함수를 사용하여 Add, Emplace, Append 함수처럼 단일 엘리먼트나 배열 사본을 주어진 인덱스에 추가할 수 있습니다.

 

StrArray.SetNum(8); // StrArray = {"Hello","Brave","World","of","Tomorrow","!","",""}

StrArray.SetNum(6); // StrArray = {"Hello","Brave","World","of","Tomorrow","!"}

마지막으로, SetNum 함수를 사용하여 컨테이너의 사이즈를 변경할 수 있습니다. 설정된 숫자가 현재 컨테이너의 엘리먼트 수보다 큰 경우에는 기본 생성자의 엘리먼트 타입을 사용해서 엘리먼트들을 새로 만들고, 작은 경우에는 엘리먼트들을 제거하기도 합니다.

 

 

2. TArray 컨테이너 반복처리

TArray 컨테이너의 엘리먼트에 대한 반복처리를 하는 방법은 여러가지가 존재합니다.

 

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

가장 추천드리는 것은 위 예제 코드에서 사용된 C++의 범위 for 기능을 사용하는 것입니다.

(위 코드에서 auto는 C++에서 제공하는 타입인데, 초기화되는 값에 맞춰서 타입이 자동으로 정해집니다. 이 auto 변수로 인해 C++에서는 보다 유연한 코드 작성이 가능합니다.)

 

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

그리고 위 예제 코드처럼 일반 인덱스 기반 반복처리도 사용할 수 있습니다.

 

for (auto Iter = StrArray.CreateConstIterator(); Iter; ++Iter)
{
    ResultStr += *Iter;
    ResultStr += TEXT(" ");
}

마지막으로 컨테이너에는 반복처리에 대한 보다 세밀한 제어가 가능하도록 별도의 반복처리 타입인 Iterator가 존재합니다. CreateIterator와 CreateConstIterator라는 함수가 있는데, CreateIterator 함수로 생성한 Iterator로는 엘리먼트에 대한 읽기와 쓰기가 모두 가능하고 CreateConstIterator 함수로 생성한 Iterator로는 엘리먼트에 대한 읽기만 가능합니다.