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 사용 등을 빌드 시점에 감지하는 방안을 제시하세요.