리플렉션(Reflection)
프로그램이 런타임에 자기 자신의 구조를 들여다보고 조작하는 능력
C++는 일반적으로 컴파일되면 타입/이름/함수 정보가 전부 날아가고 메모리 주소만 남음.
근데 리플렉션을 붙이면 해당 정보들이 날아가지 않고 런타임에 조회가 가능하다!
C++: 컴파일하면 걍 바이너리 덩어리. 리플렉션 있는 언어(C#, Java, Python..): 이름표, 타입표, 함수 목록까지 다 붙여서 실행 (런타임 조회 가능)
결국 C++는 기본적으로 리플렉션이 없기 때문에 Unreal Engine은 자체 리플렉션 시스템을 따로 만들었음.
사용법
UCLASS()UPROPERTY()UFUNCTION으로 헤더를 작성하면- 빌드 전 단계에서
UHT(Unreal Header Tool)가 헤더를 파싱하고 “아 이 클래스엔 이런 프로퍼티/함수가 있구나~” .generated.h/.gen.cpp를 자동 생성해준다.- 그럼 런타임에서
UClass*,UFunction*,FProperty*등의 메타데이터 객체가 메모리에 상주하게 됨.
UCLASS()
class UMyWidget : public UUserWidget
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
int32 Count;
UFUNCTION(BlueprintCallable)
void DoStuff();
};
이게 뭘 가능케하냐
- 이름(문자열)로 프로퍼티/함수 찾기
- Dynamic 델리게이트 작동(UFUNCTION 매크로 없으면 FindFunction 결과가 nullptr로 반환) -> 그래서 얘는 필수임.
- 에디터에서 Details 패널 설정
- 직렬화(Serialization): 리플렉션으로 프로퍼티 목록 읽어서 각 필드를 순서대로 파일에 쓰고 읽음. 없으면 저장 안됨.
- GC(Garbage Collection): GC는 어떤 객체가 어떤 UObject 포인터를 갖고있냐를 리플렉션으로 스캔. 없으면 댕글링 포인터 위험
- BP에서 C++ 함수 호출하기
- 머티리얼 파라미터
MI->SetScalarParameterValue(TEXT("Param"), 1.0f)같은거 할 때
왜 중요한가?
위에 내용이랑 비슷하지만
- Unreal Engine의 거의 모든 편의성이 리플렉션 위에서 돌아감
- BP 시각화
- 네트워크 복제
- GC 자동화
- 델리게이트 바인딩
- 에디터에서 프로퍼티 편집
- 콘솔 커맨드
단점
메모리에 올리는 만큼 비용이 있다.
UHT가 헤더 파싱해서 코드 생성하기때문에 컴파일 단계가 추가됨 -> 빌드 시간 증가
바이너리 크기 증가, 메모리 증가
그리고 호출 오버헤드가 있다고 하는데 이건 BP에서 호출, ProcessEvent, 델리게이트 BroadCast 할 때 살짝 느리다고함
결론
필요할때만 리플렉션 매크로를 붙이고, 내부 구현 디테일엔 안붙이는 로직이 깔끔쓰!
추가 궁금증.. 근데 이게 매크로인가??
맞음. 근데 특별함.
엔진 ObjectMacros.h를 보면:
#define UPROPERTY(...)
#define UFUNCTION(...)
#define USTRUCT(...)
#define UMETA(...)
#define UPARAM(...)
#define UENUM(...)
#define UDELEGATE(...)
#define RIGVM_METHOD(...)
실제론 비어있다. C++ 컴파일러 입장에선 그냥 지워지는 코드.
그럼 어떻게 동작? → UHT가 C++ 컴파일 전에 헤더를 따로 스캔해서 메타데이터 코드를 다른 파일에 생성해버림.
즉 매크로는 “여기 UHT가 읽어야 할 표식이 있다”는 마커 역할이고, 실제 수행은 UHT가 함.
댓글