이번 글은 C++의 virtual 함수의 동작에 대해 이해하고 있으면 더욱 읽기 좋습니다. 모르시는 분은 아래 글을 읽고 보시길 추천합니다.
https://ddukddaksudal.tistory.com/147
RTTI란?
RTTI는 'Run Time Type Information'의 약자입니다. 한국말로는 '실행시간 형식 정보'가 되겠네요.
말 그대로 런 타임에 어떤 객체의 타입(형식)을 알 수 있도록 하는 기능입니다.
그렇다면 왜 런타임에 객체의 타입을 알아야 할까요?
필요성
런타임에 객체의 타입을 알아야하는 상황을 만들어 보겠습니다.
class Mover
{
public:
virtual ~Mover() {}
};
class Walker : public Mover
{
public:
void Walk() { cout << "player1 walk" << endl; }
};
class Runner : public Mover
{
public:
void Run() { cout << "player2 run" << endl; }
};
void Move(Mover* _obj)
{
//_obj가 Walker 객체일 때만 Walk를 호출하고 싶음.
}
int main()
{
Mover* obj = new Walker;
Move(obj);
}
위 코드의 Move 함수에서 _obj 가 Wlaker 객체일 경우에만 무언가 동작하고 싶은 상황이라고 칩시다.
이럴 경우, 어떻게 _obj가 Walker 객체인지 알 수 있을까요? _obj가 Runner 객체일 수도 있잖아요?
이럴 때 RTTI가 필요합니다.
사용하기
RTTI를 활용하는 C++ 문법은 대표적으로 3가지가 있습니다.
- dynamic_cast 연산자 : 부모 포인터형 변수를 자식 포인터형으로 캐스팅할 수 있다.
- typeid 연산자 : 어떤 객체의 형식을 반환하는 연산자
- type_info 클래스 : typeid 연산의 결과를 저장할 수 있는 클래스.
이것들을 사용하여 위의 상황을 해결할 수 있습니다. dynamic_cast 연산을 사용해 해결해 보겠습니다.
void Move(Mover* _obj)
{
Walker* walker = dynamic_cast<Walker*>(_obj); //_obj를 Walker*로 캐스팅
if (walker) //walker가 nullptr이면 캐스팅이 잘못됐다는 뜻. 즉, Walker 객체가 아니라는 뜻.
walker->Walk();
else
{
cout << "do someting"<<endl;
}
}
이렇게 RTTI를 활용해 문제를 해결할 수 있습니다.
그렇다면 RTTI는 어떻게 런타임에 객체의 정보를 얻어오는 것일까요?
RTTI 동작 원리
virtual 함수가 존재하면 컴파일 타임에 virtual function table이 생성되어 코드영역에 등록된다고 했습니다.
그런데 사실 virtual 함수가 들어간 클래스를 컴파일할 때 virtual function table만 생겨나는 것이 아닙니다.
RTTI Complete Object Locator라는 객체가 같이 생성됩니다. 간단히 줄여서 RTTI 객체라고 합시다.
그리고 이 RTTI객체를 가리키는 포인터가 vf table의 첫 주소 바로 이전 주소에 들어가게됩니다.
그림으로 표현하면 아래와 같은 형태입니다.
그러니까 런타임에 어떤 객체가 어떤 타입인지 알고싶을 때,
해당 객체의 첫 주소에 있는 vfptr을 참조해 vftable의 첫 주소 이전의 주소에 담긴 RTTI 객체의 주소를 알아냅니다.
이 주소로 RTTI객체를 참조하여 객체의 타입을 알아내는 것입니다.
typeid, type_info
dynamic_cast의 예는 위에서 보였으니 그 외에 RTTI사용 문법에 대해 설명하겠습니다.
int main()
{
Mover* mover = new Walker;
Mover* mover2 = new Runner;
const type_info& ty = typeid(*mover);
const type_info& ty2 = typeid(*mover2);
cout << ty.name()<<", "<<ty2.name() << endl;
if (typeid(Walker) == typeid(mover2))
cout << "Walker";
else
cout << "not Walker";
}
간단하게 위의 예시만 한 번 보면 바로 이해됩니다.
typeid연산자에 Walker객체를 넘겨주고, 결과값을 const type_info& ty에 대입합니다.
그리고 ty를 통해 타입에 대한 정보를 알 수 있습니다.
또한 ==연산을 통해 같은 타입인지도 확인할 수 있습니다.
아래는 출력 결과입니다.
이 때 주의해야할 것은 typeid의 결과값은 const type_info& 형이며,
결과값을 받을 변수는 반드시 const type_info& 형이어야 합니다. const나 &가 빠져서는 안됩니다.
참고자료:
https://dataonair.or.kr/db-tech-reference/d-lounge/technical-data/?mod=document&uid=235810
https://learn.microsoft.com/ko-kr/cpp/cpp/typeid-operator?view=msvc-170
'C++' 카테고리의 다른 글
우측값 참조와 이동연산 (0) | 2024.05.02 |
---|---|
이름 숨김 (0) | 2024.04.15 |
가상함수 (2) | 2024.04.12 |
헷갈리는 C++ 질문들 (0) | 2024.04.12 |
Casting (Static, Dynamic, Reinterpret ) (0) | 2023.03.28 |