TObjectPtr로 선언하는 건 GC와 아무런 관련이 없습니다. 만약 TObjectPtr이지만 GC의 관리 대상이라면, UPROPERTY를 추가해야합니다.
언리얼의 포인터에 익숙하면 아주 당연한 이야기인데요. 생각없이 코딩하다가 실수를 해서 남겨둡니다.
해당 클래스의 주석에도 아래와 같이 설명이 되어있습니다.
TObjectPtr is a type of pointer to a UObject that is meant to function as a drop-in replacement for raw pointer member properties.
its participation in garbage collection is identical to a raw pointer to a UObject.
예제 코드를 통해 봅시다.
// .h
class TestClass{
bool IsValid();
TObjectPtr<USampleClass> SampleVal;
}
// .cpp
bool TestClass::IsValid(){
// 명시적으로 nullptr을 넣은게 아니라 GC로 수거된 경우라면, nullptr이 아니다.
if(!SampleVal){
return false;
}
// IsValid가 false임을 보장할 수 없음.
if(!IsValid(SampleVal)){
return false;
}
return true;
}
여기서 IsValid는 GC가 이미 끝난 상황에서 거의 아무것도 확인해주지 못하고, true를 리턴합니다.
우선 SampleVal = nullptr을 해서 명시적으로 포인터를 날린 경우가 아니기 때문에, SampleVal의 포인터에 NULL이 아닌 값이 들어있게 됩니다.
두번째 if문에서는, IsValid는 해당 변수 안에 있는 ObjectFlags에 RF_MirroredGarbage플래그 상태를 체크합니다. 여기서 왜 true를 리턴하는지 정확하게는 알아내지 못했는데요. 두가지 가능성만 생각해보았습니다.
플래그를 확인한다는 건, 다르게 말하면 특정 메모리 주소에 bit값이 0인지만 확인한다는 얘기입니다. GC가 끝나면서 객체가 할당되었던 영역의 메모리에 전부 0을 넣어주고 있거나, 새로운 객체가 할당되면서 해당 메모리 영역에 값이 쓰여진다면 가능한 이야기입니다.
RF_MirroredGarbage가 켜져있지 않은 게 정상적인 상황이다. (가능성 낮음)RF_MirroredGarbage가 정확히 어떤 상황에서 쓰이는 플래그인지 알지 못해서, 가능성은 있다고 생각했습니다. 원래 GC의 특정 스텝에서 다시 false로 만들어주는 플래그라거나, 어떤 경로를 통하는 경우에는 아예 쓰이지 않는 등의 가능성을 가정해보았습니다.
UPROPERTY 선언을 해주거나, TSharedPtr을 쓰거나, 상황에 맞는 판단이 필요합니다. 대체로는 UPROPERTY 선언으로 쉬운 해결이 될 듯 합니다.
여기에 한 가지 숨어있는 실용적인 부분이 있는데, 전형적으로는 살려두고자 하는 오브젝트에
UPROPERTY레퍼런스를 유지하거나, 그에 대한 포인터를TArray또는 다른 언리얼 엔진 컨테이너 클래스에 저장해야 합니다. 종종 액터와 그 컴포넌트는 예외인데, 액터는 보통 자신이 속한 레벨처럼 루트 세트로 다시 링크되는 오브젝트에, 그리고 액터의 컴포넌트는 액터 자체에 레페런싱되기 때문입니다. 액터는 자신의Destroy함수를 호출하여 명시적으로 소멸 마킹할 수 있는데, 이는 진행중인 게임에서 액터를 제거하기 위한 표준적인 방식입니다. 컴포넌트는DestroyComponent함수로 명시적으로 소멸시킬 수 있으나, 보통은 소유 액터가 게임에서 제거될 때 소멸됩니다.
출처: 언리얼 오브젝트 처리