std::shared_ptr와 유사// 철검 객체 생성. 현재 사용자: 1명
TSharedPtr<InventoryItem> player1Sword = MakeShared<InventoryItem>("철검");
// 같은 검을 참조. 현재 사용자: 2명
TSharedPtr<InventoryItem> player2Sword = player1Sword;
// 첫 번째 플레이어가 참조 해제. 현재 사용자: 1명
player1Sword = nullptr;
// 두 번째 플레이어도 참조 해제. 현재 사용자: 0명
player2Sword = nullptr;
// -> 검 객체가 자동으로 메모리에서 삭제
template<typename T>
class TSharedPtr
{
private:
T* ObjectPtr; // 실제 객체
FReferenceController* RefController; // 참조 카운트와 제어 정보
};
FReferenceController의 내부 값들SharedRefCount : 강한 참조(TSharedPtr)가 몇 개인지WeakRefCount : 약한 참조(TWeakPtr)가 몇 개인지TSharedPtr<MyClass> Ptr = MakeShared<MyClass>("Hello");
new로 받은 포인터를 TSharedPtr로 관리하고 싶을 때 사용MakeShared처럼 메모리 풀 최적화가 적용되지 않음 -> MakeShared 권장MyClass* RawPtr = new MyClass();
TSharedPtr<MyClass> Ptr = MakeShareable(RawPtr);
TSharedPtr<Quest> maybeQuest = nullptr;
if (maybeQuest.IsValid())
{
maybeQuest->StartQuest();
}
TSharedRef<Quest> definiteQuest = MakeShared<Quest>("드래곤 토벌");
definiteQuest->StartQuest();
TSharedPtr에서 파생TSharedPtr이 사라지면 TWeakPtr은 자동으로 무효화됨std::weak_ptr처럼 TSharedPtr에서 발생할 수 있는 순환 참조 문제를 해결하기 위해 등장TSharedPtr을 통해 강한 참조를 하는 경우, 참조 해제를 하더라도 참조 카운팅이 사라지지 않고, 메모리 해제가 되지 않는 문제가 발생class Parent
{
public:
TSharedPtr<Child> MyChild; // 부모 -> 자식 (강한 참조)
}
class Child
{
public:
TSharedPtr<Parent> MyParent; // 자식 -> 부모 (강한 참조)
}
void CreateReference()
{
TSharedPtr<Parent> parent = MakeShared<Parent>();
TSharedPtr<Child> child = MakeShared<Child>();
parent->MyChild = child;
child->MyParent = parent;
}
TWeakPtr로 변경을 해서, 포인터 삭제를 방해하지 않고 순환 참조 문제를 해결할 수 있다.class Parent
{
public:
TSharedPtr<Child> MyChild; // 부모 -> 자식 (강한 참조)
}
class Child
{
public:
TWeakPtr<Parent> MyParent; // 자식 -> 부모 (약한 참조로 변경)
}
void CreateReference()
{
TSharedPtr<Parent> parent = MakeShared<Parent>();
TSharedPtr<Child> child = MakeShared<Child>();
parent->MyChild = child;
child->MyParent = parent;
}
TWeakPtr 자체는 직접 객체를 사용할 수 없음Pin()을 호출해서 임시로 TSharedPtr을 얻어 객체에 안전하게 접근TSharedPtr을 반환TSharedPtr 반환TSharedPtr<T> TWeakPtr<T>::Pin() const
{
if (IsValid()) // 살아있다면 유효한 TSharedPtr 생성
{
return TSharedPtr<T>(ObjectPtr, RefController);
}
return TSharedPtr<T>(); // 이미 삭제된 경우 → 빈 SharedPtr 반환
}
if (TSharedPtr<MyClass> Pinned = WeakPtr.Pin())
{
// 객체가 아직 살아있음
Pinned->DoSomething();
}
else
{
// 객체가 이미 파괴됨
UE_LOG(LogTemp, Warning, TEXT("Object expired!"));
}
TUniquePtr만 특정 객체를 가질 수 있고, 복사는 불가능하지만 이동(move)은 가능TUniquePtr이 가질 수 없음std::move와 비슷하게 MoveTemp()를 통해 소유권 이동 가능TUniquePtr이 스코프를 벗어나면 delete가 호출됨TUniquePtr<MyClass> Ptr = MakeUnique<MyClass>(10);
Ptr->DoSomething();
// TUniquePtr<MyClass> Copy = Ptr; -> 컴파일 에러 발생
TUniquePtr<MyClass> Moved = MoveTemp(Ptr);
if (!Ptr)
{
UE_LOG(LogTemp, Log, TEXT("Ptr is null!"));
}
TUniquePtr에서 RAIITUniquePtr은 생성될 때 new로 생성된 객체를 가짐TUniquePtr이 스코프를 벗어나면 자동으로 delete 호출 -> 자동으로 자원 해제-> 예외가 발생하거나 return으로 빠져도, 자동으로 자원이 해제되어 메모리 누수 방지가 보장됨
TUniquePtr에서 Move 의미론TUniquePtr은 복사가 금지되어 있음 (복사 생성자/대입 연산자 delete)MoveTemp()를 써서 소유권을 한 번만 옮길 수 있음-> 불필요한 복사를 줄이고 성능 최적화 + 소유권 관리가 명확해짐
UObject인가?UObject라면 -> UPROPERTY() 키워드를 통해 엔진이 관리TUniquePtr 적합TSharedPtr 적합TSharedPtr 적합TUniquePtr에서 접근만 하기TSharedPtr 적합TSharedRef 적합TWeakPtr 적합TSharedPtr 적합TUniquePtr보다는 TSharedPtr)TUniquePtrTSharedPtrTSharedPtrTUniquePtr)TSharedPtr)