PART 6 · 강의 1/4

순환 참조 해결

Owner 패턴, WeakPtr 활용, Break Cycles로 순환 참조를 방지하고 해결하는 실전 전략

SECTION 01

순환 참조 탐지

순환 참조가 발생하는 일반적인 상황과 탐지 방법

흔한 순환 참조 패턴

C++ // 패턴 1: 양방향 참조 class ADialogueNPC : AActor { UPROPERTY() ADialogueNPC* DialoguePartner; // A→B }; // B도 A를 Partner로 → A↔B 순환 // 패턴 2: 매니저-아이템 역참조 class USkillManager : UObject { UPROPERTY() TArray<USkill*> Skills; // 매니저→스킬 }; class USkill : UObject { UPROPERTY() USkillManager* Manager; // 스킬→매니저 (순환!) }; // 패턴 3: 그래프 구조 class UGraphNode : UObject { UPROPERTY() TArray<UGraphNode*> Connections; // 노드 A→B→C→A 순환 가능 };
순환 참조의 실제 영향

UE의 GC는 도달 가능성 기반이므로, Root Set에서 도달 불가능한 순환 그래프는 정상적으로 수집됩니다. 진짜 문제는 순환 중 하나라도 Root Set에 도달 가능하면 전체 순환이 보호되어 의도치 않은 메모리 상주가 발생하는 것입니다.

SECTION 02

Owner 패턴으로 해결

소유권 계층으로 참조 방향을 정리하는 기법

해결: 단방향 소유권 + 약한 역참조

C++ // 해결된 SkillManager-Skill 관계 UCLASS() class USkillManager : public UObject { GENERATED_BODY() // 소유자: 강한 참조 (GC 보호) UPROPERTY() TArray<USkill*> Skills; void RemoveSkill(USkill* Skill) { Skills.Remove(Skill); // Skill은 이제 참조 없음 → 다음 GC에서 수집 } }; UCLASS() class USkill : public UObject { GENERATED_BODY() // 피소유자: 약한 역참조 (GC 방해하지 않음) UPROPERTY() TWeakObjectPtr<USkillManager> OwnerManager; void NotifyManager() { if (USkillManager* Mgr = OwnerManager.Get()) { Mgr->OnSkillCompleted(this); } } };

참조 방향 설계 원칙

원칙 // 참조 방향 설계 3원칙: 1. 소유권은 단방향 부모 → 자식: UPROPERTY (강한) 자식 → 부모: TWeakObjectPtr (약한) 2. 수명이 긴 쪽이 소유 오래 사는 객체가 짧게 사는 객체를 강하게 참조 짧게 사는 객체는 약하게 역참조 3. 관찰은 약한 참조 이벤트 리스너, 캐시, UI 바인딩 등 관찰 대상을 보호하면 안 되는 경우
SECTION 03

Break Cycles 전략

이미 존재하는 순환 참조를 명시적으로 끊는 방법

명시적 참조 해제

C++ UCLASS() class UGraphNode : public UObject { GENERATED_BODY() UPROPERTY() TArray<UGraphNode*> Connections; // 소멸 전 순환 참조 끊기 void BreakAllConnections() { for (UGraphNode* Node : Connections) { if (Node) { Node->Connections.Remove(this); } } Connections.Empty(); } virtual void BeginDestroy() override { BreakAllConnections(); Super::BeginDestroy(); } }; // 그래프 매니저에서 일괄 정리 void UGraphManager::ClearGraph() { // 모든 노드의 연결을 끊은 후 for (UGraphNode* Node : AllNodes) { Node->BreakAllConnections(); } // 배열 비우기 → 참조 해제 → GC 수집 가능 AllNodes.Empty(); }
SECTION 04

순환 참조 방지 설계 가이드

처음부터 순환 참조가 발생하지 않는 구조 설계

설계 시점의 방지 전략

상황잘못된 설계올바른 설계
부모-자식양방향 UPROPERTY부모→자식 강한, 자식→부모 약한
이벤트 리스너리스너가 소스를 강한 참조TWeakObjectPtr 또는 AddWeakLambda
그래프 구조노드 간 UPROPERTY 연결매니저가 소유, 노드 간 인덱스 참조
UI-데이터위젯이 데이터를 강하게 참조TWeakObjectPtr + 데이터 변경 콜백
SUMMARY

핵심 요약

이 강의에서 배운 내용
  • 순환 참조 자체는 UE GC에서 수집 가능하지만, Root Set 연결 시 의도치 않은 보호가 문제입니다
  • Owner 패턴: 소유자→강한 참조, 피소유자→약한 역참조가 표준 해결책입니다
  • BreakAllConnections 패턴으로 기존 순환을 BeginDestroy 시점에 명시적으로 끊습니다
  • 설계 시점에서 참조 방향을 계층적으로 설계하면 순환을 원천 방지할 수 있습니다
  • 그래프 구조에서는 매니저가 모든 노드를 소유하고, 노드 간은 인덱스로 참조합니다
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 순환 참조 생성 및 GC 테스트

두 UObject가 UPROPERTY로 서로를 참조하는 순환 참조를 만드세요. 외부 참조를 모두 제거한 후 ForceGarbageCollection()을 호출하여 UE5 GC가 순환 참조를 처리할 수 있는지 확인하세요. Mark-and-Sweep이 순환 참조에 강한 이유를 이해하세요.

실습 2: TWeakObjectPtr로 순환 참조 방지

Parent-Child 관계에서 Parent는 UPROPERTY로, Child는 TWeakObjectPtr로 역참조하는 패턴을 구현하세요. Parent가 Destroy될 때 Child의 역참조가 자동으로 무효화되는지 확인하세요.

심화 과제: 복잡한 그래프 구조의 메모리 관리

게임 내 스킬 트리, 퀘스트 의존성 그래프 등 복잡한 참조 구조를 설계할 때의 GC 전략을 수립하세요. 소유권 방향, 약한 참조 사용 위치, AddReferencedObjects 필요 지점을 포함한 참조 아키텍처 문서를 작성하세요.