C++ RTTI 직접 구현하기 (1): TypeId 기반으로 dynamic_cast 없이 타입 체크하기
🚀 들어가며
이전 글에서 C++의 dynamic_cast와 RTTI 개념을 살펴봤습니다.
그리고 중요한 사실을 확인했습니다.
❗ dynamic_cast는 안전하지만, 런타임 비용이 발생한다
또한 다운캐스팅이 왜 위험한지도 메모리 관점에서 확인했습니다.
그렇다면 자연스럽게 이런 질문이 나옵니다.
❓ dynamic_cast 없이 안전하게 타입을 확인할 수는 없을까?
이번 글에서는
👉 C++ RTTI를 직접 구현하는 첫 번째 단계로
TypeId 기반 커스텀 RTTI 시스템을 만들어보겠습니다.
🎯 목표
이번 글의 목표는 다음과 같습니다:
- dynamic_cast 없이 타입 확인
- 빠른 타입 비교
- 안전한 캐스팅 구조 구현
🧠 핵심 아이디어
핵심은 매우 간단합니다.
각 타입마다 고유한 ID를 부여하자
그리고 그 ID를 이용해서 타입을 비교합니다.
🔥 TypeId 기반 RTTI 구현
1️⃣ 베이스 클래스 설계
#pragma once
#include <memory>
class CraftObject
{
public:
virtual ~CraftObject() {}
// 현재 객체의 타입 ID 반환
virtual size_t GetType() const = 0;
// 타입 비교 함수
virtual bool Is(size_t id) const
{
return false;
}
// 템플릿 기반 타입 체크
template<typename T>
bool IsTypeOf() const
{
return Is(T::TypeId());
}
// 안전한 캐스팅 함수
template<typename T, typename U>
static std::shared_ptr<T> Cast(const std::shared_ptr<U>& object)
{
if (!object)
return nullptr;
if (object->Is(T::TypeId()))
return std::static_pointer_cast<T>(object);
return nullptr;
}
};
🔍 핵심 기능 설명
✔ GetType()
virtual size_t GetType() const = 0;
👉 각 객체가 자신의 타입 ID를 반환
✔ Is()
virtual bool Is(size_t id) const
👉 현재 객체가 특정 타입인지 확인
✔ Cast()
std::static_pointer_cast<T>
👉 dynamic_cast 대신 사용하는 안전한 캐스팅
⚙️ 매크로를 이용한 자동화
각 클래스마다 반복되는 코드를 줄이기 위해 매크로를 사용합니다.
#define TYPE_DECLARATIONS(Type, ParentType) \
using super = ParentType; \
protected: \
static size_t TypeIdClass() \
{ \
static int runtimeTypeId = 0; \
return reinterpret_cast<size_t>(&runtimeTypeId); \
} \
public: \
static size_t TypeId() \
{ \
return Type::TypeIdClass(); \
} \
virtual size_t GetType() const override \
{ \
return Type::TypeIdClass(); \
} \
virtual bool Is(size_t id) const override \
{ \
return (id == TypeIdClass()) ? true : ParentType::Is(id); \
}
🧠 가장 중요한 부분: TypeId 생성 방식
static int runtimeTypeId = 0; return reinterpret_cast<size_t>(&runtimeTypeId);
왜 주소를 사용하는가?
- static 변수는 프로그램 전체에서 단 하나만 존재
- 즉, 주소가 유일함
- 이 주소를 그대로 타입 ID로 사용
👉 결과:
“주소 자체가 타입의 고유 ID가 된다”
🎯 사용 예제
class Actor : public CraftObject
{
TYPE_DECLARATIONS(Actor, CraftObject)
};
class Player : public Actor
{
TYPE_DECLARATIONS(Player, Actor)
};
std::shared_ptr<CraftObject> obj = std::make_shared<Player>();
if (obj->Is(Player::TypeId()))
{
// Player 타입
}
if (obj->Is(Actor::TypeId()))
{
// Actor의 자식이므로 true
}
🔥 이 구조의 장점
1. 매우 빠름
👉 포인터(주소) 비교 1번
2. 상속 구조 지원
👉 ParentType::Is() 재귀 호출
3. dynamic_cast 대체 가능
👉 Cast() 함수로 안전한 캐스팅
⚠️ 한계점
이 구조에도 한계가 있습니다.
- 타입 이름 없음
- 디버깅 어려움
- 리플렉션 확장 제한
👉 즉:
❗ “엔진 수준 시스템으로는 부족”
🧩 다음 단계
이제 우리는 다음 단계로 넘어갈 준비가 되었습니다.
❓ 타입 정보를 더 풍부하게 표현할 수 없을까?
🔥 다음 글 예고
다음 글에서는:
👉 TypeInfo 기반 RTTI 구조 구현
- 타입 이름 저장
- 부모 체인 구조
- Unreal Engine 스타일 RTTI
🎮 마무리
이번 글에서는
👉 dynamic_cast 없이 타입을 확인하는 방법을 직접 구현해봤습니다.
이 구조는 실제로:
- 게임 엔진
- ECS 시스템
- 런타임 객체 관리
에서 매우 자주 사용되는 방식입니다.
👉 다음 편에서 계속됩니다.