AddReferencedObjects
FGCObject와 커스텀 GC 참조 추가로 Non-UObject 컨테이너에서 UObject를 안전하게 관리합니다
FGCObject 기본 패턴
Non-UObject 클래스에서 UObject를 GC-safe하게 참조하는 방법
FGCObject 상속
FGCObject는 UObject가 아닌 클래스가 UObject를 참조할 때 GC에게 알리는 메커니즘입니다. AddReferencedObjects를 오버라이드하여 보호할 UObject 목록을 GC에 등록합니다.
// FGCObject를 상속하여 GC 참조 등록
class FMyManager : public FGCObject
{
public:
// 이 클래스가 참조하는 UObject들을 GC에 등록
virtual void AddReferencedObjects(
FReferenceCollector& Collector) override
{
// 단일 참조 등록
Collector.AddReferencedObject(ManagedWidget);
// 배열 참조 등록
Collector.AddReferencedObjects(ManagedActors);
// TMap의 Value 등록
for (auto& Pair : DataMap)
{
Collector.AddReferencedObject(Pair.Value);
}
}
// GC 디버깅용 이름
virtual FString GetReferencerName() const override
{
return TEXT("FMyManager");
}
private:
UUserWidget* ManagedWidget = nullptr;
TArray<AActor*> ManagedActors;
TMap<FName, UDataAsset*> DataMap;
};
FGCObject 인스턴스 자체는 GC가 관리하지 않습니다. 수명은 C++ 규칙을 따릅니다. FGCObject가 파괴되면 자동으로 GC 참조 등록이 해제됩니다. 그러나 FGCObject가 파괴되지 않으면 등록된 UObject는 영원히 GC되지 않아 메모리 누수가 됩니다.
UObject의 AddReferencedObjects
UObject 내부에서 UPROPERTY 없이 참조를 등록하는 방법
static AddReferencedObjects 패턴
UObject 파생 클래스에서도 UPROPERTY 없이 raw 포인터로 보관한 UObject를 GC에 등록할 수 있습니다. static 함수로 선언해야 합니다.
UCLASS()
class UMyObject : public UObject
{
GENERATED_BODY()
public:
// static 함수로 선언 (UHT가 자동으로 토큰 스트림에 등록)
static void AddReferencedObjects(
UObject* InThis,
FReferenceCollector& Collector)
{
UMyObject* This = CastChecked<UMyObject>(InThis);
// UPROPERTY가 아닌 참조를 수동 등록
Collector.AddReferencedObject(This->HiddenRef);
// 커스텀 컨테이너 내 참조 등록
for (UObject*& Ref : This->CustomContainer)
{
Collector.AddReferencedObject(Ref);
}
// 부모 클래스 호출 (중요!)
Super::AddReferencedObjects(InThis, Collector);
}
private:
// UPROPERTY 없는 참조 - AddReferencedObjects로 보호
UObject* HiddenRef = nullptr;
// 커스텀 컨테이너 (std::vector 등)
std::vector<UObject*> CustomContainer;
};
대부분의 경우 UPROPERTY()로 충분합니다. AddReferencedObjects는 다음과 같은 특수한 상황에서만 사용합니다:
- STL 컨테이너(std::vector, std::unordered_map)에 UObject 참조 저장
- 커스텀 메모리 풀에서 UObject 참조 관리
- Native C++ 라이브러리와의 인터페이스
- 동적으로 생성되는 참조 구조 (그래프 등)
FReferenceCollector API
참조 수집기의 다양한 등록 메서드
FReferenceCollector 주요 메서드
class FReferenceCollector
{
public:
// 단일 UObject 참조 등록
template<typename T>
void AddReferencedObject(T*& Object);
// TArray<UObject*> 전체 등록
template<typename T>
void AddReferencedObjects(TArray<T*>& Objects);
// TSet<UObject*> 전체 등록
template<typename T>
void AddReferencedObjects(TSet<T*>& Objects);
// TMap에서 Value가 UObject*인 경우
template<typename K, typename V>
void AddReferencedObjects(TMap<K, V*>& Map);
// 조건부 참조 (null이면 건너뜀)
void AddReferencedObject(UObject*& Object)
{
if (Object)
{
HandleObjectReference(Object);
}
}
// 소유자 정보 포함 (디버깅용)
void AddPropertyReference(
UObject*& Object,
const FProperty* Property);
};
AddReferencedObject의 매개변수가 포인터의 레퍼런스(T*&)인 이유는, GC가 객체를 이동시키거나 null로 설정할 수 있기 때문입니다. 값으로 전달하면 원본 포인터가 업데이트되지 않아 댕글링 포인터가 됩니다.
FGCObject 실전 패턴
실전에서 자주 사용되는 FGCObject 패턴들
서브시스템 매니저 패턴
// 게임 서브시스템의 Non-UObject 매니저
class FUIManager : public FGCObject
{
public:
void PushWidget(UUserWidget* Widget)
{
WidgetStack.Push(Widget);
}
void PopWidget()
{
if (WidgetStack.Num() > 0)
{
UUserWidget* Top = WidgetStack.Pop();
Top->RemoveFromParent();
// Top은 다음 GC에서 수집 가능
}
}
virtual void AddReferencedObjects(
FReferenceCollector& Collector) override
{
Collector.AddReferencedObjects(WidgetStack);
Collector.AddReferencedObject(OverlayWidget);
}
virtual FString GetReferencerName() const override
{
return TEXT("FUIManager");
}
private:
TArray<UUserWidget*> WidgetStack;
UUserWidget* OverlayWidget = nullptr;
};
핵심 요약
- FGCObject는 Non-UObject 클래스가 UObject 참조를 GC에 등록하는 메커니즘입니다
- UObject의 static AddReferencedObjects는 UPROPERTY 없는 참조를 수동 등록합니다
- FReferenceCollector는 포인터 레퍼런스(T*&)를 받아 GC가 포인터를 업데이트할 수 있게 합니다
- GetReferencerName()을 구현하면 GC 디버깅 시 참조 출처를 추적할 수 있습니다
- 대부분의 경우 UPROPERTY()로 충분하며, AddReferencedObjects는 특수 상황에만 사용합니다
도전 과제
배운 내용을 직접 실습해보세요
UPROPERTY가 아닌 일반 멤버로 UObject*를 보유하는 클래스에서 static AddReferencedObjects를 구현하세요. FReferenceCollector::AddReferencedObject를 호출하여 GC가 해당 참조를 인식하게 만들고, GC 수집이 방지되는지 확인하세요.
TArray
TMap