PART 1 · 강의 4/7

스마트 포인터 활용

TSharedPtr, TWeakPtr, TUniquePtr로 안전한 메모리 관리

01

스마트 포인터 개요

비-UObject 클래스를 위한 메모리 관리

🔥 중요: 스마트 포인터는 비-UObject 전용!

TSharedPtr, TWeakPtr, TUniquePtr는 UObject가 아닌 일반 C++ 클래스에만 사용합니다. UObject에는 GC와 충돌하므로 사용하지 마세요!

스마트 포인터 소유권 용도
TSharedPtr 공유 (참조 카운팅) 여러 곳에서 동일 객체 참조
TWeakPtr 비소유 순환 참조 방지, 캐시
TUniquePtr 독점 (단일 소유) 명확한 단일 소유자
TWeakObjectPtr 비소유 UObject 약한 참조 (GC 호환)
02

TSharedPtr - 공유 소유권

참조 카운팅 기반 공유 포인터

C++ // 비-UObject 클래스 정의 class FMyData { public: FString Name; int32 Value; FMyData(const FString& InName, int32 InValue) : Name(InName), Value(InValue) {} }; // TSharedPtr 생성 TSharedPtr<FMyData> DataPtr = MakeShared<FMyData>(TEXT("Player"), 100); // 복사 시 참조 카운트 증가 TSharedPtr<FMyData> DataPtr2 = DataPtr; // 참조 카운트: 2 // 사용 if (DataPtr.IsValid()) { FString Name = DataPtr->Name; int32 Val = DataPtr->Value; } // 참조 카운트 확인 int32 RefCount = DataPtr.GetSharedReferenceCount(); // 마지막 TSharedPtr이 소멸될 때 객체 삭제 DataPtr.Reset(); // 참조 카운트: 1 DataPtr2.Reset(); // 참조 카운트: 0 -> 객체 삭제!

스레드 안전성 옵션

C++ // 기본: 스레드 안전하지 않음 (빠름) TSharedPtr<FMyData> FastPtr = MakeShared<FMyData>(TEXT("Fast"), 1); // 스레드 안전 버전 (멀티스레드 환경) TSharedPtr<FMyData, ESPMode::ThreadSafe> SafePtr = MakeShared<FMyData, ESPMode::ThreadSafe>(TEXT("Safe"), 2);
03

TWeakPtr - 비소유 참조

순환 참조 방지와 캐싱

C++ // TSharedPtr에서 TWeakPtr 생성 TSharedPtr<FMyData> SharedData = MakeShared<FMyData>(TEXT("Data"), 50); TWeakPtr<FMyData> WeakRef = SharedData; // TWeakPtr은 참조 카운트를 증가시키지 않음! // SharedData.GetSharedReferenceCount() == 1 // 사용 전 반드시 Pin()으로 유효성 확인 if (TSharedPtr<FMyData> Pinned = WeakRef.Pin()) { // Pinned가 유효한 동안 객체 사용 가능 FString Name = Pinned->Name; } else { // 원본 SharedPtr이 이미 해제됨 } // 순환 참조 방지 예시 class FNode { public: TSharedPtr<FNode> Child; // 자식은 소유 TWeakPtr<FNode> Parent; // 부모는 약한 참조 };
⚠️ TWeakPtr 사용 시 주의

TWeakPtr을 직접 역참조하지 마세요! 항상 Pin()으로 TSharedPtr을 얻은 후 유효성을 확인하세요.

04

TUniquePtr - 독점 소유권

단일 소유자가 명확한 경우

C++ // TUniquePtr 생성 TUniquePtr<FMyData> UniqueData = MakeUnique<FMyData>(TEXT("Unique"), 200); // 복사 불가능! // TUniquePtr<FMyData> Copy = UniqueData; // 컴파일 에러! // 이동만 가능 TUniquePtr<FMyData> MovedPtr = MoveTemp(UniqueData); // 이제 UniqueData는 nullptr // 사용 if (MovedPtr.IsValid()) { MovedPtr->Value = 300; } // 소유권 해제 (객체 반환, 포인터 무효화) FMyData* RawPtr = MovedPtr.Release(); // 이제 RawPtr을 수동으로 관리해야 함 delete RawPtr; // Reset으로 다른 객체로 교체 TUniquePtr<FMyData> Ptr = MakeUnique<FMyData>(TEXT("First"), 1); Ptr.Reset(new FMyData(TEXT("Second"), 2)); // First 삭제, Second로 교체

장점

  • 가장 가벼움 (오버헤드 없음)
  • 소유권이 명확
  • 의도치 않은 공유 방지

사용 시기

  • 팩토리 패턴의 반환 타입
  • PIMPL 패턴 구현
  • 컨테이너에 저장 (TArray<TUniquePtr>)
05

TWeakObjectPtr - UObject용

GC와 호환되는 약한 참조

C++ // UObject에 대한 약한 참조 UCLASS() class AMyActor : public AActor { GENERATED_BODY() public: // UObject를 약하게 참조 (GC가 삭제해도 안전) UPROPERTY() TWeakObjectPtr<AActor> WeakTargetRef; // 강한 참조 (GC가 삭제 안 함) UPROPERTY() TObjectPtr<AActor> StrongTargetRef; void UseTarget() { // TWeakObjectPtr 사용 전 반드시 유효성 검사 if (WeakTargetRef.IsValid()) { // .Get()으로 raw 포인터 얻기 AActor* Target = WeakTargetRef.Get(); Target->DoSomething(); } // 또는 직접 역참조 (내부적으로 IsValid 체크) if (WeakTargetRef.IsValid()) { WeakTargetRef->DoSomething(); } } void SetTarget(AActor* NewTarget) { // 약한 참조로 설정 WeakTargetRef = NewTarget; } };

TWeakObjectPtr vs UPROPERTY 비교

특성 TWeakObjectPtr UPROPERTY TObjectPtr
GC 영향 참조해도 GC 대상 될 수 있음 참조하면 GC 방지
유효성 검사 항상 IsValid() 필요 일반적으로 안전
사용 시기 캐시, 옵저버 패턴 소유권 있는 참조
💡 사용 시나리오

TWeakObjectPtr: AI가 타겟을 추적하지만, 타겟이 죽으면 자연스럽게 참조가 무효화되길 원할 때

TObjectPtr + UPROPERTY: 인벤토리 아이템처럼 명확히 소유하고 있는 객체

06

TSoftObjectPtr - 지연 로딩

에셋의 비동기 로딩

C++ UCLASS() class AMyActor : public AActor { GENERATED_BODY() public: // 소프트 참조 - 에셋이 자동 로드되지 않음 UPROPERTY(EditAnywhere) TSoftObjectPtr<UStaticMesh> MeshToLoad; // 소프트 클래스 참조 UPROPERTY(EditAnywhere) TSoftClassPtr<AActor> ActorClassToSpawn; void LoadAsset() { // 동기 로딩 (블로킹) UStaticMesh* Mesh = MeshToLoad.LoadSynchronous(); if (Mesh) { // 메시 사용 } } void LoadAssetAsync() { // 비동기 로딩 (권장) FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager(); StreamableManager.RequestAsyncLoad( MeshToLoad.ToSoftObjectPath(), FStreamableDelegate::CreateUObject( this, &AMyActor::OnAssetLoaded ) ); } void OnAssetLoaded() { if (UStaticMesh* LoadedMesh = MeshToLoad.Get()) { // 로드된 메시 사용 } } };
SUMMARY

핵심 요약

  • TSharedPtr/TWeakPtr/TUniquePtr — 비-UObject 전용! UObject에 사용 금지
  • TSharedPtr — 공유 소유권, 참조 카운팅, MakeShared로 생성
  • TWeakPtr — 순환 참조 방지, Pin()으로 유효성 확인 후 사용
  • TUniquePtr — 독점 소유권, 복사 불가, 이동만 가능
  • TWeakObjectPtr — UObject 약한 참조, GC와 호환, IsValid() 필수
  • TSoftObjectPtr — 에셋 지연 로딩, 비동기 로딩 지원
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: TSharedPtr로 비-UObject 데이터 관리

RPG의 스킬 트리 노드를 일반 C++ 클래스 FSkillNode로 구현하고, TSharedPtr로 관리하세요. MakeShared로 생성하고 GetSharedReferenceCount()로 참조 카운트를 확인하세요.

실습 2: TWeakObjectPtr로 AI 타겟 추적

적 AI가 추적하는 타겟을 TWeakObjectPtr로 참조하세요. 타겟이 Destroy()되었을 때 IsValid() 체크로 안전하게 처리하고, 새 타겟을 자동 탐색하는 로직을 구현하세요.

심화 과제: TSoftObjectPtr로 비동기 에셋 로딩

RPG 무기의 메시를 TSoftObjectPtr로 참조하고, FStreamableManager::RequestAsyncLoad()로 비동기 로딩을 구현하세요. 장비 변경 시 이전 메시를 해제하고 새 메시를 로드하는 전체 플로우를 완성하세요.