728x90
RTTI
- RTTI는 RunTime Type Information의 약자이다. 이를 번역하면 실시간 타입 정보라는 의미가 된다.
- 일반적으로 변수의 이름이나 구조체, 클래스의 타입은 컴파일러가 컴파일하는 동안에만 필요하기 때문에 컴파일을 거친 후에는 이 정보들이 남아있지 않다.
- 하지만, 특정한 객체의 정보, 예를 들면 클래스의 이름이나 어떤 클래스를 상속했는지 등을 알아야 할 때가 있는데 이를 위해 필요한 것이 실시간 타입 정보(RunTime Type Information - RTTI)이다.
- RTTI는 오직 다형성 클래스, 즉 최소 하나 이상의 가상 함수(virtual function)를 가진 클래스에 대해서만 생성된다.
- 컴파일러는 각 다형성 클래스에 대해 std::type_info 객체를 생성하고, 이 객체에 대한 포인터를 vtable(가상 함수 테이블)에 삽입하여 객체 인스턴스마다 vptr을 통해 접근할 수 있도록 한다.
표준 C++의 RTTI는 dynamic_cast이다.
- dynamic_cast는 형변환이 가능한 경우에만 포인터를 새로운 포인터 타입으로 형변환 한다.
- dynamic_cast가 성공하면 새로운 타입의 포인터가 반환되며, 실패하면 nullptr이 반환된다.
class Player
{
public:
Player() {}
virtual ~Player() {}
};
class Knight : public Player
{
public:
Knight() {}
virtual ~Knight() {}
};
int main()
{
Knight* knight = new Knight();
Player* player = dynamic_cast<Player*>(knight);
}
형변환이 올바르다?
- 형변환이 올바르다. 또는 바르지 못하다는 것을 어떤 의미일까?
- 형변환하고자 하는 포인터 타입이 해당 객체 자체의 타입이거나, 상위 클래스 중에 하나인 경우에는 올바른 형변환이다.
- dynamic_cast는 한 번에 두 가지 일을 처리한다.
- 시도하는 형변환 작업이 올바른지를 검사한다.
- 포인터를 새로운 타입으로 형변환한다.
class Player
{
public:
Player() {}
virtual ~Player() {}
};
class Knight : public Player
{
public:
Knight() {}
virtual ~Knight() {}
};
class Archer
{
public:
Archer() {}
virtual ~Archer() {}
};
int main()
{
// 잘 동작함
// knight는 player를 상속하기 때문에, knight -> player(업 캐스팅)이 아무 문제없음
Knight* knight = new Knight();
Player* player = dynamic_cast<Player*>(knight);
// 동작하지 않음
// 서로 상속관계에 있지 않기 때문
Archer* archer = dynamic_cast<Archer*>(knight);
}
typeid, type_info
- dynamic_cast의 메커니즘은 특정한 객체의 대한 상속 관련 정보를 줄 뿐만 아니라, 서로 다른 타입 간의 변환도 가능하게 한다.
- typeid는 객체에 대한 보다 자세한 정보를 제공한다.
- typeid 연산자는 객체의 타입에 대한 정보를 얻을 때 사용한다.
- 객체에 대한 모든 정보는 typeid가 반환하는 type_info 내에 저장된다.
- type_info 안에는 클래스 이름이 저장돼 있다.
int main()
{
Player* player = new Knight();
Knight* knight = dynamic_cast<Knight*>(player);
const type_info& pInfo = typeid(player);
const type_info& kInfo = typeid(knight);
std::cout << "player 클래스 이름: " << pInfo.name() << "\n";
std::cout << "knight 클래스 이름: " << kInfo.name() << "\n";
}

typeid의 또 다른 쓰임새 중 하나는 클래스 타입의 비교이다.
두 객체가 같은 클래스로부터 생성됐는지를 확인하려고 할 때 다음과 같이 구성할 수 있다.
int main()
{
const type_info& info1 = typeid(*obj1);
const type_info& info2 = typeid(*obj2);
if (info1 == info2)
{
// 같은 타입인 경우, 원하는 로직을 수행
}
}
typeid를 사용할 때 주의할 점은, 다형성이 구현된 타입에서만 적용해야 한다는 점이다.
즉, 적어도 하나의 가상 함수를 가진 클래스에만 적용해야 한다.
C++에서는 클래스에 가상 함수(virtual)가 하나라도 있다면, 그 클래스와 이를 상속받는 클래스들은 모두 가상함수테이블(Virtual Function Table) 메모리에 올라간다.
우리가 dynamic_cast를 사용하면 vtable에 담긴 RTTI 구조체와 별도의 상속 계층 정보 테이블을 사용해 안전하게 타입을 판별하고, 같으면 형변환이 되고 다르면 nullptr을 반환한다.
vptr -> vtable -> type_info 순서로 vtable이 해당 객체의 타입 정보를 type_info를 얻는다.
커스텀 RTTI
- 표준 C++ RTTI 시스템의 가장 큰 단점은 성능이다. dynamic_cast
- 하지만, 최근에는 성능이 많이 개선되었기 때문에 dynamic_cast를 사용하는 경우에는 프로파일링을 통해 사용을 결정하는 것이 바람직하다.
- 특히 상속 구조가 단순한 경우에는 오버헤드가 무시할 수 있는 수준인 경우가 많다.
- 간헐적으로 사용하는 보통의 경우에는 문제가 되지 않을 수 있지만, 게임 프로그래밍 환경처럼 속도가 중요한 경우에는 결코 달갑지 않은 문제다.
커스텀 RTTI는, 말 그대로 RTTI를 커스텀으로 구현해서, 미리 클래스에 어떤 정보를 심어두어 이를 사용하는 방식으로 RTTI를 사용하는 방식이다.
상용 엔진인 언리얼엔진의 경우, UCLASS 같은 시스템을 통해 자체적으로 RTTI와 캐스팅까지 구현하여 사용한다.
- 고유 숫자 값 할당을 위해 전역 메모리 주소 사용
- 같은 클래스 타입이 공유하는 고유 값이 필요하기 때문
- 단일 상속을 선언할 수 있는 매크로 작성
- 현재는 부모 계층 검색을 위해 재귀적 탐색 방법을 사용하고 있는데, 동적 배열이나 해시테이블을 사용해 캐싱을 적용하면 성능을 더 개선시킬 수 있음
// 상속 관계에 있는 클래스 간의
// 동적(실행중에) 형변환을 빠르게 하기 위해 사용하는 클래스.
// RunTime-Type-Information.
class ENGINE_API RTTI
{
public:
// 타입 정보 반환 순수 가상 함수
virtual const size_t& TypeIdInstance() const = 0;
virtual bool Is(const size_t id) const
{
return false;
}
template<typename T>
T* As()
{
// RTTI를 상속받는 클래스는 반드시 TypeIdClass가 있어야 함.
if (Is(T::TypeIdClass()))
{
return (T*)this;
}
return nullptr;
}
template<typename T>
const T* As() const
{
if (Is(T::TypeIdClass()))
{
return (T*)this;
}
return nullptr;
}
};
// 자손, 부모를 인자로 받음
#define RTTI_DECLARATIONS(Type, ParentType) \
public: \
using super = ParentType; \
virtual const size_t& TypeIdInstance() const { return Type::TypeIdClass(); } \
static const size_t TypeIdClass() \
{ \
static int runTimeTypeId = 0; \
return reinterpret_cast<size_t>(&runTimeTypeId); \
} \
virtual bool Is(const size_t id) const \
{ \
if (id == TypeIdClass()) \
{ \
return true; \
} \
else \
{ \
return ParentType::Is(id); \
} \
}
728x90
'📕Programming > 📝C/C++' 카테고리의 다른 글
| [C / C++] C 스타일 파일 입출력 (0) | 2025.07.23 |
|---|---|
| [C / C++] static (0) | 2025.07.23 |
| [C / C++] 스택 & 힙 메모리 할당 과정 (0) | 2025.07.20 |
| [C / C++] 동적 라이브러리 (dynamic link library) (0) | 2025.07.18 |
| [C / C++] 정적 라이브러리 (static library) (0) | 2025.07.18 |