스마트 포인터 활용
TSharedPtr, TWeakPtr, TUniquePtr로 안전한 메모리 관리
스마트 포인터 개요
비-UObject 클래스를 위한 메모리 관리
TSharedPtr, TWeakPtr, TUniquePtr는 UObject가 아닌 일반 C++ 클래스에만 사용합니다. UObject에는 GC와 충돌하므로 사용하지 마세요!
| 스마트 포인터 | 소유권 | 용도 |
|---|---|---|
| TSharedPtr | 공유 (참조 카운팅) | 여러 곳에서 동일 객체 참조 |
| TWeakPtr | 비소유 | 순환 참조 방지, 캐시 |
| TUniquePtr | 독점 (단일 소유) | 명확한 단일 소유자 |
| TWeakObjectPtr | 비소유 | UObject 약한 참조 (GC 호환) |
TSharedPtr - 공유 소유권
참조 카운팅 기반 공유 포인터
// 비-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 -> 객체 삭제!
스레드 안전성 옵션
// 기본: 스레드 안전하지 않음 (빠름)
TSharedPtr<FMyData> FastPtr = MakeShared<FMyData>(TEXT("Fast"), 1);
// 스레드 안전 버전 (멀티스레드 환경)
TSharedPtr<FMyData, ESPMode::ThreadSafe> SafePtr =
MakeShared<FMyData, ESPMode::ThreadSafe>(TEXT("Safe"), 2);
TWeakPtr - 비소유 참조
순환 참조 방지와 캐싱
// 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을 직접 역참조하지 마세요! 항상 Pin()으로 TSharedPtr을 얻은 후 유효성을 확인하세요.
TUniquePtr - 독점 소유권
단일 소유자가 명확한 경우
// 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>)
TWeakObjectPtr - UObject용
GC와 호환되는 약한 참조
// 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: 인벤토리 아이템처럼 명확히 소유하고 있는 객체
TSoftObjectPtr - 지연 로딩
에셋의 비동기 로딩
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())
{
// 로드된 메시 사용
}
}
};
핵심 요약
- TSharedPtr/TWeakPtr/TUniquePtr — 비-UObject 전용! UObject에 사용 금지
- TSharedPtr — 공유 소유권, 참조 카운팅, MakeShared로 생성
- TWeakPtr — 순환 참조 방지, Pin()으로 유효성 확인 후 사용
- TUniquePtr — 독점 소유권, 복사 불가, 이동만 가능
- TWeakObjectPtr — UObject 약한 참조, GC와 호환, IsValid() 필수
- TSoftObjectPtr — 에셋 지연 로딩, 비동기 로딩 지원
도전 과제
배운 내용을 직접 실습해보세요
RPG의 스킬 트리 노드를 일반 C++ 클래스 FSkillNode로 구현하고, TSharedPtr
적 AI가 추적하는 타겟을 TWeakObjectPtr
RPG 무기의 메시를 TSoftObjectPtr