Programming/C++ & Unreal

C++ RTTI 구현

장형이 2021. 5. 2. 21:50

개요

RTTI는 Run Time Type Information의 약자로 프로그램이 실행된 시점에 Type을 알 수 있는 기능이다.

 

기본

#include <iostream>
using namespace std;

class A {

};
class B {

};

int main() {
	A a;
	B b;

	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	/* 출력
	class A
	class B
	*/

	return 0;
}

기본적으로 typeid()으로 타입을 가져와서, name()을 사용하면 const char*를 얻을 수 있다.

 

구현

// Main.cpp
#include <iostream>
#include "RTTI.h"

class A {
	DECLARE_RTTI;
};

class B {
	DECLARE_RTTI;
};

IMPLEMENT_RTTI(A);
IMPLEMENT_RTTI(B, A);

using namespace std;
int main() {
	A a;
	B b;
	
	cout << a.GetRtti()->GetClassName() << endl;
	cout << b.GetRtti()->GetClassName() << endl;
	/* 출력
	A
	B
	*/

	cout << b.GetRtti()->IsChildOf(a.GetRtti()) << endl;
	cout << a.GetRtti()->IsChildOf(b.GetRtti()) << endl;
	/* 출력
	1
	0
	*/

	cout << b.GetRtti()->IsParentOf(a.GetRtti()) << endl;
	cout << a.GetRtti()->IsParentOf(b.GetRtti()) << endl;
	/* 출력
	0
	1
	*/

	return 0;
}

typeid 기능을 비슷하게 따라 해 보았다.

 

// RTTI.h
#pragma once
#include "MacroOverloading.h" // 매크로 오버로딩을 위한 함수들이 있다. (PP_MACRO_OVERLOAD) - https://rextester.com/discussion/ONP80107/C-Macro-overload

// 헤더 정의 작성용 매크로이다. 클래스의 static 멤버로 구현된다.
#define DECLARE_RTTI private: \
static CRTTI m_rtti;\
public:\
static CRTTI* GetRtti() { return &m_rtti; }

// static 멤버를 구현하기 위해서 소스 파일에서 사용하기 위한 매크로이다.
#define IMPLEMENT_RTTI_1(CLASS_NAME) CRTTI CLASS_NAME::m_rtti = CRTTI(#CLASS_NAME)
#define IMPLEMENT_RTTI_2(CLASS_NAME,PARENT_CLASS) CRTTI CLASS_NAME::m_rtti = CRTTI(#CLASS_NAME, PARENT_CLASS::GetRtti())
#define IMPLEMENT_RTTI(...) PP_MACRO_OVERLOAD(IMPLEMENT_RTTI, __VA_ARGS__)

class CRTTI {
private:
	const char* m_className;
	CRTTI* m_parent;

public:
	CRTTI(const char* cClassName_, CRTTI* ptrParent_ = nullptr) {
		m_className = cClassName_;
		m_parent = ptrParent_;
	}

	const char* GetClassName() {
		return m_className;
	}
	
	// m_parent부터 거슬러 올라가면서, 인자를 찾을 수 있는지 확인한다.
	bool IsChildOf(CRTTI* ptrRtti) {
		CRTTI* ptrCurrentRtti = m_parent;

		while (ptrCurrentRtti != nullptr)
		{
			if (ptrCurrentRtti == ptrRtti) {
				return true;
			}

			ptrCurrentRtti = ptrCurrentRtti->m_parent;
		}

		return false;
	}

	// 인자부터 거슬러 올라가면서, this를 찾을 수 있는지 확인한다.
	bool IsParentOf(CRTTI* ptrRtti) {
		CRTTI* ptrCurrentRtti = ptrRtti;

		while (ptrCurrentRtti != nullptr)
		{
			if (ptrCurrentRtti == this) {
				return true;
			}

			ptrCurrentRtti = ptrCurrentRtti->m_parent;
		}

		return false;
	}

	bool IsSame(CRTTI* ptrRtti) {
		return ptrRtti == this;
	}
};

 

용도 및 후기

하이레벨(어플리케이션단) 코딩에서 굳이 런타임에 타입을 알아내서 분기를 타야 한다면, 높은 확률로 코드를 잘못 짜고 것일 듯하다..

실전에서의 RTTI는 로그 작성할 때 말고는 딱히 유용하게 써먹을 곳은 모르겠지만, 따라 하는 게 크게 어렵지 않고 재미있었다.

(구현이라 써놓고 너무 부끄러울 정도로 대충 짠 느낌... -////-)