UE5

[UE5] TWeakObjectPtr

Pyxis 2024. 11. 6. 13:21

[ UE5 : TWeakObjectPtr ]

 

언리얼 엔진에서 제공하는 스마트 포인터 중, GC와 관련된 기능을 하는 포인터.

UPROPERTY() 를 통해 참조하면 강한 참조라 칭하는데, 참조되면 해당 메모리는 GC에 의해 해제되지 않는다.

하지만 모든 UObject를 UPROPRETY()로 참조한다면, 잠깐 참조하고 생명 주기와 연관없어 필요하지 않는데도 많은 곳에서 참조되어 해제되지 않거나, 한 번에 엄청난 크기로 뭉쳐서 해제될 것이다.

예를 들면, 두개의 오브젝트가 상호간에 UPROPERTY()로 참조하고 있다면, 둘 다 파괴되어야 같이 해제될 것이다.

최악의 상황인 댕글링 포인터 문제는 생기지 않겠지만, GC 비용을 늘리게 되는 것.

그래서 약하게 참조하는 것이고, TWeakObjectPtr로 쥐고 있어도 GC의 레퍼런스에는 영향을 미치지 않는다.

게다가, 접근 전 유효성 검사가 가능하고 해당 오브젝트가 소멸된 경우 nullptr로 설정할 수 있어 댕글링 포인터 문제로 부터도 안전하다.

 

위 설명이 공식문서를 기반한 설명이고, 엔진 코드 주석에 의하면 다음과 같다.

FWeakObjectPtr is a weak pointer to a UObject. 
FWeakObjectPtr은 UObject에 대한 약한 포인터입니다.

It can return nullptr later if the object is garbage collected.
객체가 GC되면 나중에 nullptr을 반환할 수 있습니다.

It has no impact on if the object is garbage collected or not.
객체가 GC되는지 여부에 영향을 미치지 않습니다.

It can't be directly used across a network.
네트워크를 통해 직접 사용될 수 없습니다.

Most often it is used when you explicitly do NOT want to prevent something from being garbage collected.
대부분 GC되는 것을 명시적으로 방지하고 싶지 않을 때 사용됩니다.

 

더 깊게 들어가보자.

[ 내부적으로 어떻게 동작하길래 해당 오브젝트가 소멸됐는지 확인할 수 있는가? ]

UObject* Internal_Get(bool bEvenIfPendingKill) const
{
    FUObjectItem* const ObjectItem = Internal_GetObjectItem();
    return ((ObjectItem != nullptr) && GUObjectArray.IsValid(ObjectItem, bEvenIfPendingKill)) ? (UObject*)ObjectItem->Object : nullptr;
}
    
FUObjectItem* Internal_GetObjectItem() const
{
    if (ObjectSerialNumber == 0)
    {
        return nullptr;
    }
    if (ObjectIndex < 0)
    {
        return nullptr;
    }
    // check nullptr using GUObjectArray.
    FUObjectItem* const ObjectItem = GUObjectArray.IndexToObject(ObjectIndex);
    if (!ObjectItem)
    {
        return nullptr;
    }
    if (!SerialNumbersMatch(ObjectItem))
    {
        return nullptr;
    }
    return ObjectItem;
}

 

내부적으로 ObjectIndex와 SerialNumber를 저장하게 되는데

이것은 해당 Object가 엔진에서 전역으로 관리하는 GUObjectArray의 index, 그리고 객체를 구분하는 고유한 number이다.

WeakObjectPtr.h
UObjectArray.h

 

그래서 접근하기 전에, GUObjectArray에 먼저 접근하게 되는데

1) 먼저 ObjectIndex가 유효한지 확인하여 해당 객체가 파괴되었는지 확인하고,

2) ObjectIndex가 유효하다고 하더라도, 이미 다른 객체가 할당되었을 수 있으므로 SerialNumber를 통해 동일한 객체인지 확인한다.

1), 2)에 의해 유효하지 않을경우 nullptr을 반환한다.

 

내부 작동 방식을 알았으니 사용 중 유의해야할 점이 생겼다.

Get()을 통해 유효성을 확인할 때마다 전역으로 관리되는 GUObjectArray에 접근하므로 잠재적인 cache miss를 발생시킬 수 있다.

한 번만 접근하고, 해당 포인터를 저장해두고 쓰는 것이 좋다.

if (UObject* Object = ObjectWeakPtr.Get())
{
	/* ... */
    
}

 

또한, Get()은 operator-> 에서도 실행되므로 dereference 할 때에도 유의해야 한다.

 

 

 

 

[ Reference ]

공식 문서 : 

https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-object-handling-in-unreal-engine?application_version=5.4#%EB%A0%88%ED%8D%BC%EB%9F%B0%EC%8A%A4%EC%9E%90%EB%8F%99%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8

 

GC에서의 순환 참조 : 

https://algorfati.tistory.com/75

 

Optimizing TWeakObjectPtr usage : 

https://prosser.io/optimizing-tweakobjectptr-usage/

'UE5' 카테고리의 다른 글

[UE5] 공식 문서 주요사항 메모  (0) 2024.11.19
[UE5] CreateDefaultSubobject, NewObject, SpawnActor  (0) 2024.11.03
[UE5] LineTrace Cost  (3) 2024.10.18
[UE5] Widget Blueprint가 추가된 후 보이지 않음  (0) 2024.07.29
[UE5] UE_LOG, FName  (0) 2024.07.25