PART 6 · 강의 4/4

GC 안티패턴 모음

흔한 실수들, FindObject 남용, 글로벌 참조, UPROPERTY 누락 등 반면교사로 삼을 패턴 총정리

SECTION 01

UPROPERTY 누락 (가장 흔한 실수)

GC 크래시의 #1 원인

안티패턴: UPROPERTY 없는 UObject*

C++ - 잘못된 코드 UCLASS() class UMyComponent : public UActorComponent { GENERATED_BODY() // 안티패턴 1: UPROPERTY 없는 멤버 UObject* CachedTarget; // GC가 모름! // 안티패턴 2: 로컬에 장기 보관 void CacheTarget(AActor* Target) { CachedTarget = Target; // GC가 Target을 수집하면 → CachedTarget은 댕글링! } };
C++ - 올바른 코드 UCLASS() class UMyComponent : public UActorComponent { GENERATED_BODY() // 해결 1: UPROPERTY 추가 (강한 참조 필요 시) UPROPERTY() TObjectPtr<UObject> CachedTarget; // 해결 2: TWeakObjectPtr (GC 보호 불필요 시) TWeakObjectPtr<AActor> WeakTarget; void UseTarget() { if (AActor* Target = WeakTarget.Get()) { // 안전하게 사용 } } };
UPROPERTY 체크리스트

코드 리뷰 시 모든 UObject* 멤버 변수에 다음 중 하나가 있는지 확인하세요:

  • UPROPERTY() - 강한 참조
  • TWeakObjectPtr<T> - 약한 참조
  • TSoftObjectPtr<T> - 소프트 참조
  • AddReferencedObjects에서 등록 - 수동 관리
SECTION 02

FindObject / LoadObject 남용

전역 검색의 GC 관련 위험성

안티패턴: FindObject로 캐싱 우회

C++ - 잘못된 코드 // 안티패턴: 매 프레임 FindObject 호출 void UMySystem::Tick() { // 매 프레임 전역 검색 → 느리고 GC 비안전 UMyData* Data = FindObject<UMyData>( nullptr, TEXT("/Game/Data/ImportantData")); if (Data) { // 이 Data는 UPROPERTY가 아니므로 GC 대상 // 다음 GC에서 수집될 수 있음 Data->Process(); } }
C++ - 올바른 코드 // 해결: 초기화 시 한 번 로드하고 UPROPERTY에 캐싱 UPROPERTY() TObjectPtr<UMyData> CachedData; void UMySystem::Initialize() { CachedData = LoadObject<UMyData>( nullptr, TEXT("/Game/Data/ImportantData")); } void UMySystem::Tick() { if (CachedData) { CachedData->Process(); // UPROPERTY로 보호됨 } }
SECTION 03

글로벌 참조와 AddToRoot 남용

전역 상태의 GC 영향과 올바른 대안

안티패턴: 전역 UObject*

C++ - 잘못된 코드 // 안티패턴 1: 글로벌 raw 포인터 static UMyManager* GMyManager = nullptr; // GC가 추적 불가 → 댕글링 또는 AddToRoot 필요 // 안티패턴 2: AddToRoot 남용 UMyObject* Obj = NewObject<UMyObject>(); Obj->AddToRoot(); // RemoveFromRoot를 잊으면 → 영구 메모리 누수 // 안티패턴 3: 싱글턴 UObject에 AddToRoot class UGlobalState { static UGlobalState* Instance; static UGlobalState* Get() { if (!Instance) { Instance = NewObject<UGlobalState>(); Instance->AddToRoot(); // 영원히 살아남음 } return Instance; } };
C++ - 올바른 코드 // 해결 1: UGameInstanceSubsystem 사용 UCLASS() class UMySubsystem : public UGameInstanceSubsystem { GENERATED_BODY() // 자동으로 GameInstance 수명에 맞춰 관리 // GC도 자동, AddToRoot 불필요 }; // 해결 2: GEngine->GameSingleton // 프로젝트 설정에서 지정, 엔진이 관리 // 해결 3: 필요 시 AddToRoot + 확실한 정리 void Shutdown() { if (Instance) { Instance->RemoveFromRoot(); // 반드시! Instance = nullptr; } }
SECTION 04

기타 안티패턴 모음

실전에서 자주 발견되는 GC 관련 실수들

안티패턴 카탈로그

안티패턴 모음 // 안티패턴 4: TSharedPtr<UObject> 사용 TSharedPtr<UMyObject> Bad; // 절대 안 됨! // → TObjectPtr<UMyObject> 또는 UPROPERTY() 사용 // 안티패턴 5: Lambda에서 this 캡처 (GC 위험) AsyncTask(ENamedThreads::GameThread, [this]() { DoSomething(); // this가 GC되었으면 크래시! }); // → TWeakObjectPtr<UMyObject> WeakThis(this); 캡처 // 안티패턴 6: GC 중 UObject 생성 void MyCallback() { if (IsGarbageCollecting()) { // NewObject() 호출하면 assertion 실패! return; } NewObject<UMyObject>(); } // 안티패턴 7: BeginDestroy에서 새 UObject 생성 void UMyObject::BeginDestroy() { // NewObject(...); // GC 중 → 크래시! Super::BeginDestroy(); } // 안티패턴 8: Timer에서 raw this 사용 GetWorld()->GetTimerManager().SetTimer(Handle, this, &UMyObj::OnTimer, 5.0f, false); // this가 GC되면 타이머 콜백에서 크래시 // → this가 5초 안에 파괴될 수 있다면 Lambda + WeakPtr 사용
GC 안전 코드 리뷰 체크리스트
  • 모든 UObject* 멤버에 UPROPERTY 또는 TWeakObjectPtr이 있는가?
  • Lambda/콜백에서 this를 직접 캡처하지 않는가?
  • AddToRoot()를 사용했다면 RemoveFromRoot()가 보장되는가?
  • FindObject/LoadObject 결과를 UPROPERTY에 캐싱하는가?
  • TSharedPtr에 UObject를 저장하지 않는가?
  • BeginDestroy/FinishDestroy에서 NewObject를 호출하지 않는가?
SUMMARY

핵심 요약

이 강의에서 배운 내용
  • UPROPERTY 누락은 GC 크래시의 #1 원인이며, 모든 UObject* 멤버를 점검해야 합니다
  • FindObject/LoadObject 결과는 UPROPERTY 변수에 캐싱하여 GC에서 보호합니다
  • AddToRoot 남용 대신 Subsystem, GameSingleton 등 엔진 수명 관리 패턴을 활용합니다
  • Lambda에서 TWeakObjectPtr 캡처로 GC된 객체 접근을 방지합니다
  • TSharedPtr<UObject>는 절대 사용하면 안 되며, GC와 참조 카운팅이 충돌합니다
PRACTICE

도전 과제

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

실습 1: 5가지 GC 안티패턴 식별 및 수정

다음 안티패턴을 의도적으로 코드에 만들고 수정하세요: (1) UPROPERTY 없는 UObject* 멤버, (2) 불필요한 AddToRoot, (3) 생성자 밖의 CreateDefaultSubobject, (4) 매 틱 NewObject 호출, (5) 강한 참조 순환. 각 수정 전후의 영향을 분석하세요.

실습 2: 코드 리뷰 체크리스트 작성

팀 프로젝트에서 GC 관련 문제를 사전에 방지하기 위한 코드 리뷰 체크리스트를 작성하세요. UPROPERTY 사용 여부, 포인터 타입 선택, 객체 수명 관리, 에셋 참조 방식 등 10가지 이상의 체크 항목을 포함하세요.

심화 과제: Static Analysis 규칙 설계

UE5 프로젝트에서 GC 안티패턴을 자동 탐지하는 정적 분석 규칙을 설계하세요. Clang-Tidy 커스텀 체크 또는 UnrealHeaderTool 경고를 활용하여 UPROPERTY 없는 UObject 포인터, 위험한 AddToRoot 사용 등을 빌드 시점에 감지하는 방안을 제시하세요.