C++

RTTI

춤추는수달 2024. 4. 27. 19:23

이번 글은 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가지가 있습니다. 

  1. dynamic_cast 연산자 : 부모 포인터형 변수를 자식 포인터형으로 캐스팅할 수 있다.
  2. typeid 연산자 : 어떤 객체의 형식을 반환하는 연산자
  3. 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

 

C++ 프로그래밍 : 런타임 타입 정보(Runtime Type Information)

C++ 프로그래밍 런타임 타입 정보(Runtime Type Information) 클래스 사이 타입 변환에 대해 두 번에 걸쳐 자세한 소개를 했다. 간단히 떠올려보면 강제 타입 변환을 비롯해 static_cast, reinterpret_cast에 관련

dataonair.or.kr

https://learn.microsoft.com/ko-kr/cpp/cpp/typeid-operator?view=msvc-170

 

typeid 연산자

자세한 정보: typeid 연산자

learn.microsoft.com

 

'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