1. Cast란?
Cast는 언리얼 엔진의 UObject 시스템에서 제공하는 핵심 기능 중 하나로, 특정 클래스 타입으로 객체 포인터를 안전하게 변환(다운캐스팅)하는 데 사용된다. C++의 dynamic_cast와 유사한 역할을 하지만, 언리얼 엔진의 리플렉션 시스템을 기반으로 동작하여 훨씬 뛰어난 성능을 보인다.
캐스팅이 성공하면 해당 타입의 유효한 포인터를 반환하고, 실패하면 nullptr를 반환하여 프로그램의 비정상적인 종료를 방지한다.
// SomeActor는 AActor 타입의 포인터
ACharacter* MyCharacter = Cast<ACharacter>(SomeActor);
if (MyCharacter)
{
// SomeActor가 실제로는 ACharacter이거나 그 자식 클래스일 경우,
// 이 블록이 실행됩니다.
MyCharacter->Jump();
}
2. Cast의 동작 계층 구조
계층 1: 사용자 코드 (User Code)
개발자는 게임 로직에서 특정 UObject가 원하는 타입인지 확인하고 싶을 때 Cast를 사용한다.
void AMyTriggerBox::OnOverlapBegin(AActor* OverlappedActor, AActor* OtherActor)
{
// 트리거 박스에 들어온 액터가 플레이어인지 확인
APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(OtherActor);
if (PlayerCharacter)
{
PlayerCharacter->ApplyDamage(10.f);
}
}
계층 2: 엔진 API (Casts.h)
Cast<T>()는 Engine/Source/Runtime/CoreUObject/Public/Templates/Casts.h에 정의된 템플릿 함수다. 이 함수는 다양한 C++ 타입에 대해 오버로딩 및 템플릿 특수화를 통해 최적의 캐스팅 방법을 제공한다. UObject를 상속받은 클래스의 경우, 내부적으로 UObject::IsA 함수를 호출하는 로직으로 이어진다.
// Casts.h의 UObject 관련 Cast 구현 일부 (개념적)
template <typename To, typename From>
FORCE_INLINE To* Cast(From* Src)
{
// UObject에서 파생된 타입인지 확인 후,
// 내부 캐스팅 함수를 호출한다.
return TCastImpl<To, From>::DoCast(Src);
}
// TCastImpl의 UObject 특수화 버전 (개념적)
template <typename To, typename From>
struct TCastImpl<...UObject...>
{
static To* DoCast(From* Src)
{
// IsA 함수로 타입 검사 후, static_cast로 변환
return Src && Src->IsA(To::StaticClass()) ? static_cast<To*>(Src) : nullptr;
}
};
계층 3: UObject의 IsA 함수 (Object.h)
UObject::IsA 함수는 객체가 특정 클래스(UClass)의 인스턴스이거나 해당 클래스로부터 파생되었는지를 검사하는 핵심 함수다. 이 함수는 dynamic_cast와 달리 C++ RTTI(Run-Time Type Information)를 사용하지 않는다.
// UObject.h의 IsA 함수 (개념적)
bool UObject::IsA(const UClass* SomeBase) const
{
// 객체의 UClass 정보를 가져온다.
const UClass* ThisClass = GetClass();
// 부모 클래스 체인을 따라 올라가며 SomeBase와 일치하는지 확인
return ThisClass->IsChildOf(SomeBase);
}
계층 4: UClass의 상속 체인 검사 (Class.h)
UClass::IsChildOf 함수는 리플렉션 데이터의 일부인 클래스 상속 구조 정보를 이용해 검사를 수행한다. 모든 UClass는 자신의 부모 UClass에 대한 포인터(SuperStruct)를 가지고 있으며, 이 포인터를 따라가며 상속 계층을 확인한다.
// UClass.h의 IsChildOf 함수 (개념적)
bool UClass::IsChildOf(const UStruct* SomeBase) const
{
// 자기 자신부터 시작해서
for (const UStruct* TempStruct = this; TempStruct; TempStruct = TempStruct->GetSuperStruct())
{
// 부모 체인 중에 SomeBase가 있다면 true
if (TempStruct == SomeBase)
{
return true;
}
}
return false;
}
이 방식은 단순한 포인터 비교의 연속이므로 매우 빠르다.
계층 5: UHT (Unreal Header Tool) 코드 생성
이 모든 과정이 가능하려면 각 클래스의 타입 정보(UClass)와 상속 구조가 엔진에 등록되어 있어야 한다. 이 역할을 Unreal Header Tool (UHT)이 담당한다.
개발자가 UCLASS() 매크로를 클래스에 사용하면, UHT는 빌드 과정에서 헤더 파일을 파싱하여 리플렉션 데이터를 담은 .generated.h 파일을 자동으로 생성한다. 이 파일에는 StaticClass() 함수, 부모 클래스 정보, 멤버 변수 및 함수 정보 등이 포함된다. StaticClass()는 해당 클래스의 UClass 싱글톤 객체를 반환하는 정적 함수로, Cast와 IsA에서 클래스를 식별하는 데 사용된다.
3. Cast vs. C++ dynamic_cast
| 구분 | UE5 Cast |
C++ dynamic_cast |
|---|---|---|
| 대상 | UObject를 상속받은 클래스 |
가상 함수가 하나 이상 포함된 다형적 C++ 클래스 |
| 성능 | 매우 빠름. 사전 생성된 상속 체인(포인터 배열)을 순회하는 방식. | 상대적으로 느림. RTTI 정보를 이용해 런타임에 타입 문자열 비교 등 복잡한 연산을 수행. |
| 메커니즘 | 언리얼 리플렉션 시스템 (UClass, SuperStruct) |
C++ RTTI (Run-Time Type Information) |
| 메모리 | 리플렉션 데이터가 메모리를 차지하지만, 이는 직렬화, 블루프린트 등 다용도로 활용됨. | RTTI 정보가 클래스마다 추가적인 메모리 오버헤드를 유발. |
| 사용처 | 언리얼 엔진 프로젝트의 UObject 관련 코드 |
일반적인 C++ 프로젝트 또는 언리얼에서 UObject가 아닌 클래스 |
결론: 언리얼 엔진 환경에서 UObject 파생 클래스를 다룰 때는 성능상의 이점과 엔진 시스템과의 호환성을 위해 dynamic_cast 대신 Cast를 사용하는 것이 필수적입니다.
4. 언제, 어떻게 사용해야 하는가?
- 안전한 다운캐스팅이 필요할 때
상위 타입 포인터를 하위 타입 포인터로 변환해야 할 때 사용한다.Cast는 타입 검사를 포함하므로 가장 안전하고 일반적인 방법이다.AActor* Actor = GetSomeActor(); if (AMySpecificActor* SpecificActor = Cast<AMySpecificActor>(Actor)) { SpecificActor->DoSomethingSpecific(); } - 인터페이스 구현 여부를 확인할 때
Cast는 클래스 상속뿐만 아니라 인터페이스 구현 여부를 확인하는 데도 사용할 수 있다.if (IMyInterface* MyInterface = Cast<IMyInterface>(SomeActor)) { MyInterface->Execute_MyInterfaceFunction(SomeActor); } - 캐스팅 성공이 보장될 때:
CastChecked
코드의 논리상 캐스팅이 100% 성공해야만 하는 상황에서는CastChecked를 사용할 수 있다.CastChecked는 실패 시nullptr를 반환하는 대신 어설트(assert)를 발생시켜 개발 과정에서 문제를 즉시 발견하도록 돕는다. 릴리즈 빌드에서는 성능을 위해 검사 없이static_cast처럼 동작할 수 있으므로, 실패 가능성이 조금이라도 있다면 사용해서는 안 된다.// 이 액터는 반드시 APlayerController여야만 함 APlayerController* PC = CastChecked<APlayerController>(GetController());
5. 요약
UE5의 Cast는 UObject 기반의 객체를 다른 타입으로 안전하게 변환하는 기능입니다. C++의 dynamic_cast와 역할은 비슷하지만, 내부 동작 방식과 성능에서 큰 차이가 있습니다.
dynamic_cast는 C++ 표준 기능인 RTTI를 사용해 런타임에 클래스 정보를 비교하는데, 이는 상대적으로 느린 작업입니다. 반면, 언리얼의 Cast는 UHT가 빌드 시점에 미리 생성해둔 리플렉션 데이터를 사용합니다. 이 데이터에는 각 클래스의 부모가 누구인지에 대한 정보가 단순한 포인터 연결 리스트처럼 저장되어 있습니다. Cast는 이 상속 체인을 따라가며 포인터를 몇 번 비교하는 것만으로 타입 검사를 끝내기 때문에 dynamic_cast보다 월등히 빠릅니다.
따라서 언리얼 엔진에서 UObject를 다룰 때는, 엔진의 최적화된 리플렉션 시스템을 최대한 활용하고 일관성을 유지하기 위해 반드시 Cast를 사용해야 합니다.
'Unreal' 카테고리의 다른 글
| 언리얼 엔진 리플리케이션 Replication (1) | 2025.10.24 |
|---|---|
| 언리얼 엔진 리플렉션 시스템 Unreal Engine Reflection System (0) | 2025.10.23 |
| CDO (Class Default Object) (0) | 2025.10.21 |
| UObject (0) | 2025.10.21 |
| UPROPERTY와 UFUNCTION의 차이 (0) | 2025.10.16 |
