TIL_097 : 언리얼 스마트 포인터

김펭귄·2026년 1월 15일

Today What I Learned (TIL)

목록 보기
97/139

1. 언리얼 스마트 포인터

1.1. TSharedPtr

// 생성
TSharedPtr<InventoryItem> sword = MakeShared<InventoryItem>("철검");

// 다른 포인터도 함께 공유
TSharedPtr<InventoryItem> anotherSword = sword;

// Null인지 체크
if (sword.IsValid())
{
	// 접근하여 사용
    sword->Attack();
}

// 해제
sword = nullptr;
anotherSword = nullptr; // 참조 카운트 0되어 자동 삭제
  • MakeShared<T>(이름)으로 생성

  • UObject가 아니므로, 전역함수 IsValid(ptr)이 아니라, 멤버함수인 .IsValid()를 써야함

  • 내부적으로 데이터(힙에 존재)를 가리키는 원시포인터와, 참조카운트를 관리하는 객체를 가리키는 포인터 2개를 가짐

template<typename T>
class TSharedPtr 
{
private:
    T* ObjectPtr;                     // 실제 객체
    FReferenceController* RefController; // 참조 카운트와 제어 정보
};
  • 원시포인터보다 생성/공유/소멸할 때 오버헤드가 존재하나, 거의 미미하므로 이거 사용

1.2. TSharedRef

  • C++에서처럼, 포인터 대신 참조자로도 사용 가능

  • 마찬가지로 포인터는 Null이 되지만, 레퍼런스는 안 되고 초기화도 꼭 해주어야함

// 생성시 초기화 필수
TSharedRef<Quest> definiteQuest = MakeShared<Quest>("드래곤 토벌");
definiteQuest->StartQuest(); 
  • 참조자이기 때문에 IsValid 없이 바로 사용 가능

  • 포인터보다 안전성 면에서 좋음

// 참조자에서 포인터로의 변환은 바로 가능
TSharedRef<Quest> QuestRef = MakeShared<Quest>("드래곤 토벌");
TSharedPtr<Quest> QuestPtr = QuestRef; 

// 포인터에서 참조자로의 변환은 Null포인터면 불가능(에러)
TSharedPtr<Quest> MaybeQuest = MakeShared<Quest>("보물 찾기");
if (MaybeQuest.IsValid())
{
	// 변환 과정 필요
    TSharedRef<Quest> QuestRef = MaybeQuest.ToSharedRef(); 
}
  • 참조자가 보통 더 안전하므로, 게임에서 무조건 존재해야만 하는 객체들(UI의 버튼/위젯)에 사용

1.3. TWeakPtr

class Parent
{
public:
    TSharedPtr<Child> MyChild; // 강한 참조
};

class Child
{
public:
    TWeakPtr<Parent> MyParent; // 약한 참조
};
  • C++의 weak_ptr과 거의 동일

  • 순환참조 문제 해결 가능

// 사용하려면 Pin으로 SharedPtr을 얻어야 함 (승격)
TSharedPtr<Parent> ParentPtr = MyParent.Pin();
if (ParentPtr.IsValid()) 	// Null인지 꼭 확인
{
    ParentPtr->foo();
    ParentPtr = nullptr;	// 사용 다 했으면 참조 제거
}
else
{
     // Pin 실패: 부모 객체가 이미 삭제됨
    UE_LOG(LogTemp, Warning, TEXT("부모는 이미 삭제됨!"));
}
  • Pin으로 SharedPtr로 승격받게 되면, 참조카운트도 하나 올라가서, 사용하는동안 메모리 해제를 방지함

  • UI에서처럼 부모-자식 관계가 있을 때 부모는 자식을 책임지고, 자식은 부모에게 알림만 전해주는 관계에서 주로 사용

1.4. TUniquePtr

TUniquePtr<Weapon> myWeapon = MakeUnique<Weapon>("총");

// 복사 불가능
TUniquePtr<Weapon> anotherWeapon = myWeapon; // 컴파일 에러!

// 이동은 가능
TUniquePtr<Weapon> newOwner = MoveTemp(myWeapon); // 소유권 이전
  • RAII를 준수

  • MoveTemp로 복사비용 없이 이동 시킴

  • 이동되면 이전의 포인터는 nullptr이 됨

  • UniquePtr로 가리키는 객체를 SharedPtr로 또 가리키면 안 됨 (그 반대도 안 됨)

  • 각 포인터는 서로를 모르기 때문에 그냥 메모리 해제할 수 있고, 이미 해제한거에 접근할 수도 있기 때문

profile
반갑습니다

0개의 댓글