// 생성
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; // 참조 카운트와 제어 정보
};
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();
}
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에서처럼 부모-자식 관계가 있을 때 부모는 자식을 책임지고, 자식은 부모에게 알림만 전해주는 관계에서 주로 사용
TUniquePtr<Weapon> myWeapon = MakeUnique<Weapon>("총");
// 복사 불가능
TUniquePtr<Weapon> anotherWeapon = myWeapon; // 컴파일 에러!
// 이동은 가능
TUniquePtr<Weapon> newOwner = MoveTemp(myWeapon); // 소유권 이전
RAII를 준수
MoveTemp로 복사비용 없이 이동 시킴
이동되면 이전의 포인터는 nullptr이 됨
UniquePtr로 가리키는 객체를 SharedPtr로 또 가리키면 안 됨 (그 반대도 안 됨)
각 포인터는 서로를 모르기 때문에 그냥 메모리 해제할 수 있고, 이미 해제한거에 접근할 수도 있기 때문