언리얼 UObject 동작 원리 정리 (CDO, Reflection, GC, Serialization)

언리얼 C++ 개념 학습 노트. 강의 2.1~2.7 개념만. UObject가 왜 이렇게 도는지 헷갈릴 때 다시 보기.

왜 Unreal C++인가

Native C++은 메모리 직접 관리라 최적화엔 좋지만 잘못 쓰면 누수/크래시. C# 같은 후발 언어는 GC, 리플렉션을 언어에 내장. 언리얼은 매크로 문법으로 Native C++에 이 기능들을 얹음. 이게 적용된 클래스가 UObject, 이 문법이 Unreal C++.

핵심 함정: UObject가 아니면 Content Browser에 인식 안 됨. 일반 C++ 클래스(부모 None)는 에디터가 못 봄. UCLASS() + .generated.h가 있어야 UObject.

CDO (Class Default Object)

엔진 초기화 시 클래스마다 CDO 생성. CDO는 생성자 로직대로 초기화된 템플릿 개체. 개체를 새로 만들 때 처음부터 만드는 게 아니라 CDO를 복제 → 메모리 효율.

가장 중요한 포인트: 생성자는 PIE 누르기 전에 이미 한 번 호출됨(CDO 생성 시점). PIE 누르면 또 호출(실제 개체). 그래서 생성자에 무거운 로직이나 월드 의존 로직 넣으면 안 됨. 초기화는 Init() 같은 이벤트 함수에서. CDO는 GetDefault()로 언제든 접근 가능.

이벤트 함수와 Super 호출

특정 이벤트 발생 시 자동 호출되는 함수가 이벤트 함수(생성자, Init, Shutdown 등). override 시 Super::함수() 먼저 호출해야 함. 엔진 엔지니어가 작성한 코드가 먼저 돌아야 엔진 루틴이 깨지지 않음.

방어 코드: check / ensure / IsValid

  • check(조건): 거짓이면 크래시. checkf()로 메시지 추가
  • ensure(조건): 거짓이면 알리지만 크래시는 안 냄(복구 로직 필요 지점 찾을 때). ensureMsgf()로 메시지
  • IsValid(): UObject 포인터가 유효한지. nullptr만 검사하면 Pending Kill/GC 대상을 못 거름. 셋 다 체크해줌

판단 기준: "false일 수 있고 그땐 그냥 넘어가면 됨" → 예외처리(if). "false일 수가 없음, 즉시 알려야 함" → checkf(). checkf 잘 쓰면 if 줄어서 코드 깔끔.

Reflection (리플렉션)

런타임에 자기 자신을 조사하는 기능. GC, 리플리케이션, 시리얼라이즈의 근간. UObject만 가능.

용어: 멤버 변수 = 속성(Property, UPROPERTY()), 멤버 함수 = 함수(Function, UFUNCTION()). 이 매크로가 런타임 에디터에 메타데이터 전달.

  • StaticClass(): 컴파일 타임 클래스 정보
  • GetClass(): 런타임 클래스 정보
  • 다형성 상황(부모 포인터가 자식 가리킴)에서 둘이 다를 수 있음
  • 이름으로 속성/함수 찾아 호출 가능(FindPropertyByName, FindFunctionByName, ProcessEvent)

객체 생성은 new가 아니라 NewObject<>() 써야 함. 언리얼 메모리 체계에 등록돼야 하기 때문.

Garbage Collection (Mark-Sweep)

동작: 루트에서 시작 → 참조하는 개체 Mark → Mark된 개체가 참조하는 것 또 Mark 반복 → Mark 안 된 것(아무도 참조 안 함) 회수(Sweep).

GUObjectArray: 모든 UObject 정보를 담은 전역 자료구조. RootSet 플래그(참조 없어도 회수 안 됨), Garbage 플래그(회수 예정). GCCycle 주기마다 몰아서 회수.

로우 포인터 문제 해결 정리:

  • 메모리 누수 → GC가 자동 해결
  • 댕글링 포인터 → IsValid()로 검사
  • 와일드 포인터 → UPROPERTY() 붙은 속성은 자동 nullptr 초기화

가장 중요한 함정: UObject 포인터 멤버에 UPROPERTY()를 안 붙이면 GC가 추적을 못 함. 참조로 안 잡혀서 살아있어야 할 개체가 회수되거나, 초기화가 안 됨. UObject 멤버엔 UPROPERTY() 습관.

Serialization (직렬화)

오브젝트 그래프(개체가 다른 개체를 속성으로 참조하는 관계)를 바이트 스트림으로 변환. 역은 Deserialization. 용도: 게임 상태 저장/복원, 블루프린트 복붙, 네트워크 위치 재현.

API: 아카이브 클래스 + << 연산자. 메모리(FMemoryReader/Writer), 파일(FArchiveFileReader/Writer), Json(별도 모듈).

USTRUCT: 언리얼 구조체 매크로. 리플렉션 제한적(UPROPERTY만 됨, UFUNCTION 안 됨). 직렬화 제공. 스택 메모리라 NewObject 불가. 데이터 저장/전송용으로 많이 씀.

UObject 직렬화는 Serialize(FArchive&) override해서 Super 호출 후 Ar << 멤버. Json은 텍스트라 읽기 쉽고 가볍지만 타입이 적고(문자/숫자/불리언/널/배열/오브젝트) 극한 효율은 불가. 웹 통신 사실상 표준.

+ Recent posts