Skip to content

UE5 C++ 컨테이너 완전 가이드

개요 — 왜 STL 대신 UE 컨테이너를 쓰는가

Section titled “개요 — 왜 STL 대신 UE 컨테이너를 쓰는가”

언리얼 엔진은 std::vector, std::unordered_map, std::unordered_set 대신 자체 컨테이너를 제공합니다. UE 컨테이너는 다음 장점을 가집니다.

항목STLUE 컨테이너
메모리 할당자OS 기본FMemory (커스텀 추적 가능)
UPROPERTY 직렬화불가가능 (TArray, TMap, TSet)
Blueprint 노출불가가능
멀티플랫폼 일관성구현 의존엔진 보장

TArray<T>는 동적 크기 배열로, C++ std::vector에 대응합니다. 메모리가 연속적으로 배치되어 캐시 친화적입니다.

#include "Containers/Array.h"
TArray<int32> Scores;
// 추가
Scores.Add(100);
Scores.Add(85);
Scores.Emplace(72); // Add보다 효율적 (이동 생성자 활용)
// 예약 (재할당 방지)
TArray<FVector> Positions;
Positions.Reserve(1000);
// 삽입 / 제거
Scores.Insert(90, 0); // 인덱스 0에 삽입
Scores.RemoveAt(1); // 인덱스 1 제거 (순서 유지, O(n))
Scores.RemoveAtSwap(0); // 스왑 후 제거 (순서 변경, O(1))
// 크기 및 초기화
int32 Count = Scores.Num();
Scores.SetNum(5); // 크기 조정 (0으로 채움)
Scores.Init(0, 10); // 10개 요소를 0으로 초기화
TArray<FString> Names = { TEXT("Alice"), TEXT("Bob"), TEXT("Carol") };
// 선형 탐색
int32 Idx = Names.IndexOfByKey(TEXT("Bob")); // 없으면 INDEX_NONE
bool bFound = Names.Contains(TEXT("Carol"));
// 조건 검색
FString* Found = Names.FindByPredicate([](const FString& Name)
{
return Name.StartsWith(TEXT("A"));
});
// 필터링
TArray<FString> Filtered = Names.FilterByPredicate([](const FString& Name)
{
return Name.Len() > 3;
});
TArray<int32> Nums = { 5, 2, 8, 1, 9 };
// 기본 오름차순 정렬
Nums.Sort();
// 람다 커스텀 정렬
Nums.Sort([](const int32 A, const int32 B) { return A > B; }); // 내림차순
// 안정 정렬 (동일 요소의 상대 순서 유지)
Nums.StableSort();
// UObject 배열 정렬 예시
TArray<TObjectPtr<AActor>> Actors;
Actors.Sort([](const TObjectPtr<AActor>& A, const TObjectPtr<AActor>& B)
{
return A->GetActorLocation().Z < B->GetActorLocation().Z;
});
TArray<FString> Tags = { TEXT("Enemy"), TEXT("Boss"), TEXT("Flying") };
// 범위 기반 for
for (const FString& Tag : Tags)
{
UE_LOG(LogTemp, Log, TEXT("Tag: %s"), *Tag);
}
// 인덱스 기반
for (int32 i = 0; i < Tags.Num(); ++i)
{
UE_LOG(LogTemp, Log, TEXT("[%d] %s"), i, *Tags[i]);
}
// 역방향 순회 (제거 시 안전)
for (int32 i = Tags.Num() - 1; i >= 0; --i)
{
if (Tags[i].Contains(TEXT("Boss")))
{
Tags.RemoveAt(i);
}
}
UCLASS()
class MYGAME_API AEnemySpawner : public AActor
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, Category = "Spawn")
TArray<TSubclassOf<AEnemy>> EnemyClasses;
UPROPERTY(VisibleAnywhere, Category = "State")
TArray<TObjectPtr<AEnemy>> ActiveEnemies;
};

TMap<K, V>는 키-값 쌍을 저장하는 해시 테이블입니다. std::unordered_map에 대응하며, 삽입·조회가 평균 O(1)입니다.

TMap<FString, int32> ItemCounts;
// 추가
ItemCounts.Add(TEXT("Sword"), 3);
ItemCounts.Add(TEXT("Shield"), 1);
ItemCounts.Emplace(TEXT("Potion"), 10);
// 조회
int32* CountPtr = ItemCounts.Find(TEXT("Sword")); // 없으면 nullptr
int32 Count = ItemCounts.FindOrAdd(TEXT("Arrow")); // 없으면 기본값 추가
// 존재 확인
bool bHasShield = ItemCounts.Contains(TEXT("Shield"));
// 제거
ItemCounts.Remove(TEXT("Shield"));
// 크기
int32 Size = ItemCounts.Num();
TMap<FName, float> AttributeMap;
AttributeMap.Add(TEXT("Strength"), 10.f);
AttributeMap.Add(TEXT("Agility"), 8.f);
AttributeMap.Add(TEXT("Intelligence"), 12.f);
// 키-값 쌍 순회
for (const TPair<FName, float>& Pair : AttributeMap)
{
UE_LOG(LogTemp, Log, TEXT("%s: %.1f"), *Pair.Key.ToString(), Pair.Value);
}
// 키만 순회
TArray<FName> Keys;
AttributeMap.GetKeys(Keys);
// 값만 순회
TArray<float> Values;
AttributeMap.GenerateValueArray(Values);
// 하나의 키에 여러 값이 필요할 때
TMultiMap<FString, FString> TagMap;
TagMap.Add(TEXT("Enemy"), TEXT("Ground"));
TagMap.Add(TEXT("Enemy"), TEXT("Flying"));
TagMap.Add(TEXT("NPC"), TEXT("Merchant"));
TArray<FString> EnemyTypes;
TagMap.MultiFind(TEXT("Enemy"), EnemyTypes);
// EnemyTypes = { "Flying", "Ground" }

TSet<T>는 고유 요소만 저장하는 해시 집합입니다. std::unordered_set에 대응하며, 중복 제거와 빠른 포함 여부 확인에 최적입니다.

TSet<FName> UnlockedAbilities;
// 추가 (중복 무시됨)
UnlockedAbilities.Add(TEXT("Dash"));
UnlockedAbilities.Add(TEXT("Dash")); // 두 번 추가해도 1개만 저장
UnlockedAbilities.Add(TEXT("Fireball"));
// 포함 확인 (O(1))
bool bHasDash = UnlockedAbilities.Contains(TEXT("Dash"));
// 제거
UnlockedAbilities.Remove(TEXT("Fireball"));
int32 Size = UnlockedAbilities.Num();
TSet<int32> SetA = { 1, 2, 3, 4 };
TSet<int32> SetB = { 3, 4, 5, 6 };
// 교집합
TSet<int32> Intersection = SetA.Intersect(SetB); // { 3, 4 }
// 합집합
TSet<int32> Union = SetA.Union(SetB); // { 1, 2, 3, 4, 5, 6 }
// 차집합
TSet<int32> Difference = SetA.Difference(SetB); // { 1, 2 }

요구사항컨테이너이유
순서 있는 목록, 랜덤 인덱스 접근TArray연속 메모리, O(1) 인덱스
키로 빠른 값 조회TMap해시 O(1) 평균 조회
중복 없는 집합, 빠른 존재 확인TSet해시 O(1) 포함 확인
하나의 키에 여러 값TMultiMap중복 키 허용

5. 실전 예시 — 인벤토리 시스템

Section titled “5. 실전 예시 — 인벤토리 시스템”
InventoryComponent.h
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Inventory")
void AddItem(FName ItemID, int32 Quantity = 1);
UFUNCTION(BlueprintCallable, Category = "Inventory")
bool RemoveItem(FName ItemID, int32 Quantity = 1);
UFUNCTION(BlueprintPure, Category = "Inventory")
int32 GetItemCount(FName ItemID) const;
UFUNCTION(BlueprintPure, Category = "Inventory")
bool HasItem(FName ItemID) const;
private:
// 아이템 ID → 수량 (빠른 조회)
UPROPERTY(VisibleAnywhere, Category = "Inventory")
TMap<FName, int32> ItemInventory;
// 장착 중인 아이템 (중복 불필요)
UPROPERTY(VisibleAnywhere, Category = "Inventory")
TSet<FName> EquippedItems;
// 획득 이력 (순서 유지)
UPROPERTY(VisibleAnywhere, Category = "Inventory")
TArray<FName> AcquisitionLog;
};
InventoryComponent.cpp
void UInventoryComponent::AddItem(FName ItemID, int32 Quantity)
{
int32& Count = ItemInventory.FindOrAdd(ItemID);
Count += Quantity;
AcquisitionLog.AddUnique(ItemID); // 중복 없이 이력 추가
}
bool UInventoryComponent::RemoveItem(FName ItemID, int32 Quantity)
{
int32* CountPtr = ItemInventory.Find(ItemID);
if (!CountPtr || *CountPtr < Quantity)
{
return false;
}
*CountPtr -= Quantity;
if (*CountPtr <= 0)
{
ItemInventory.Remove(ItemID);
EquippedItems.Remove(ItemID); // 장착 해제
}
return true;
}
int32 UInventoryComponent::GetItemCount(FName ItemID) const
{
const int32* CountPtr = ItemInventory.Find(ItemID);
return CountPtr ? *CountPtr : 0;
}
bool UInventoryComponent::HasItem(FName ItemID) const
{
return ItemInventory.Contains(ItemID);
}

  • TArray: 순서가 중요하거나 인덱스로 접근이 잦을 때 — 범용 컨테이너 1순위
  • TMap: 키로 즉시 값을 찾아야 할 때 — O(1) 조회
  • TSet: 중복 없이 요소를 관리하고 빠른 포함 확인이 필요할 때
  • Reserve()로 예약하면 대량 추가 시 재할당을 방지해 성능이 향상됩니다.
  • UPROPERTY로 선언하면 에디터 노출 및 직렬화가 자동으로 지원됩니다.