RTTI는 Run-Time Type Information의 약자로써, 실행시간에 객체의 타입 정보를 얻게 하는 C++의 확장? 정도 이다.
자바나 C#의 경우에는 리플렉션의 축소판 정도라고 생각하면 되겠다.
우선은 RTTI를 위하여 구현해야 하는 기능을 보자.
- 실행 시에 알려지지 않은 클래스의 이름과 크기를 얻을 수 있어야 한다.
- 실행 시에 알려지지 않은 클래스를 동적으로 생성할 수 있어야 한다.
우선 실행 시에 클래스의 이름을 얻는 방법부터 보자.
이를 위해서는 딱 두가지의 처리만 해주면 된다.
1. 자신의 클래스의 이름을 저장하는 정적변수를 만든다.
2. 그 이름을 리턴하는 GetClassName()을 오버라이드(override)한다.
소스로 보는 것이 더 확실할 것이다.
class CObject {
public:
// 자신의 클래스이름을 반환하는 메서드를 만든다.
virtual char* GetClassName() const { return NULL; }
};
class CSomeObject : public CObject {
public:
// 자신의 클래스 이름을 반환하는 메서드를 오버라이드한다.
virtual char* GetClassName() const { return lpszClassName; }
// 자신의 클래스의 이름을 저장하는 정적변수를 만든다.
static char lpszClassName[];
};
char CSomeObject::lpszClassName[] = "CSomeObject";
void main() {
// 사용하는 방법.
CObject *p;
p = new CSomeObject;
cout << p->GetClassName(); // 동적으로 만들어진
// 클래스의 이름을 알 수 있다!!
delete p;
}
실행결과는 다음과 같을 것이다.
CSomeObject
이것을 매크로로 정의하여 편하게 사용하여 보자.
그 매크로는 바로 두구두구두구두구!!!!
DECLARE_CLASSNAME(s)
IMPLEMENT_CLASSNAME(s)
위의 두 매크로들이다.
선언은 아래와 같이 되어있다.
#define DECLARE_CLASSNAME(s) static char lpszClassName[]
#define IMPLEMENT_CLASSNAME(s) char s##::lpszClassName=(#s)
간단하게 사용하는 예제를 적어본다.
(위의 예제와 똑같은 것이므로 주석은 안 달았다.)
class CObject {
public:
virtual char* GetClassName() const { return NULL; }
};
class CSomeObject : public CObject {
public:
DECLARE_CLASSNAME(CSomeObject);
virtual char* GetClassName() const { return lpszClassName; }
};
IMPLEMENT_CLASSNAME(s);
void main() {
CObject *p;
p = new CSomeObject;
cout << p->GetClassName();
delete p;
}
이제 실행 시에 클래스의 이름을 알아내는 방법은 알았다.
책에서는 저렇게 하였으나, 개인적인 생각으로는
DECLARE_CLASSNAME(s) 매크로를
#define DECLARE_CLASSNAME(s) static char lpszClassName[];
virtual char* GetClassName() const { return lpszClassName; }
으로 하는 것이 나을 꺼 같다.
-------------------------------------------------------------------------------
이제 동적 생성을 지원하기 위한 조건을 알아보도록 하겠습니다.
우선 동적 생성을 지원하는 클래스를 만들기 위해서
코드 자동 생성기가 해야 하는 작업을 구체적으로 살펴보면,
1. 객체의 클래스 이름을 알 수 있어야 되며,
2. 객체의 크기를 알 수 있어야 되며,
3. 클래스를 동적으로 생성하기 위한 함수를 갖어야 한다.
위의 작업이 가능하도록 구조체를 만들어 봅니다.
struct CRuntimeClass {
char m_lpszClassName[21]; // 객체의 클래스 이름
int m_nObjectSize; // 객체의 크기
CObject* (*pfnCreateObject)(); // 실제 객체를 생성하기 위한 함수 포인터
CObject* CreateObject; // 객체를 생성하기 위한 함수(인터페이스)
};
// 단순히 pfnObject 함수포인터를 랩핑(wrapping)한다.
CObject* CRuntimeClass::CreateObject() { return (*pfnObject)(); }
이제 문제는 자신을 동적으로 생성하는 코드를 어떤 방식으로 준비하느냐 하는 것이다.
해답은 정적(static) 맴버 함수를 이용하는 것이다.
(객체가 만들어지기 전에 이미 메모리상에 존재하므로 가능하게 된다.)
그럼 CObject를 고쳐보자.
class CObject {
virtual CRuntimeClass* GetRuntimeClass() const { return NULL; }
static CRuntimeClass classObject;
virtual ~CObject() { }
protected:
CObject() { printf("CObject constructor\n"); }
};
CRuntimeClass CObject::classObject = { "CObject", sizeof(CObject), NULL };
그리고 실제로 상속받아서 클래스를 구현하는 경우를 살펴보자.
class CAlpha : public CObject {
public:
virtual CRuntimeClass* GetRuntimeClass() const { return &classCAlpha; }
static CRuntimeClass classCAlpha;
static CObject* CreateObject();
protected:
CAlpha() { printf("CAlpha constructor\n"); }
};
CRuntimeClass CAlpha::classCAlpha = { "CAlpha", sizeof(CAlpha), CAlpha::CreateObject };
CObject* CAlpha::CreateObject() { return new CAlpha; /* 자신을 동적으로 생성한다. */ }
이제 이것을 어떻게 쓰는지만 알면 해결이다.
#define RUNTIME_CLASS(class_name) (&class_name::class##class_name)
void main(void) {
CRuntimeClass* pRTCAlpha = RUNTIME_CLASS(CAlpha);
// CRuntimeClass* pRTCAlpha = &CAlpha::classCAlpha;
CObject* pObj1;
pObj1 = pRTCAlpha->CreateObject();
printf("CAlpha class=%s\n", pObj1->GetRuntimeClass()->m_lpszClassName);
delete pObj1;
}
실제로 MFC에서는 매크로로 만들어서 사용하고 있다. (afx.h에 있다.)
#define DECLARE_DYNAMIC(class_name) static CRuntimeClass class##class_name
#define IMPLEMENT_DYNAMIC(class_name) CRuntimeClass \
class_name::class##class_name = { \
(#class_name), \
sizeof(class_name), \
class_name::CreateObject };
#define DECLARE_DYNCREATE(class_name) static CObject* CreateObject();
#define IMPLEMENT_DYNCREATE(class_name) CObject* \
class_name::CreateObject() { \
return new class_name; \
}
그럼 마지막으로 매크로를 이용해서 종합해보자.
class CAlpha : public CObject {
public:
virtual CRuntimeClass* GetRuntimeClass() const { return &classCAlpha; }
DECLARE_DYNAMIC(CAlpha);
DECLARE_DYNCREATE(CAlpha);
protected:
CAlpha() { printf("CAlpha constructor\n"); }
};
IMPLEMENT_DYNAMIC(CAlpha);
IMPLEMENT_DYNCREATE(CAlpha);
class CBeta : public CObject {
public:
virtual CRuntimeClass* GetRuntimeClass() const { return &classCBeta; }
DECLARE_DYNAMIC(CBeta);
DECLARE_DYNCREATE(CBeta);
protected:
CBeta() { printf("CBeta constructor\n");
};
IMPLEMENT_DYNAMIC(CBeta);
IMPLEMENT_DYNCREATE(CBeta);
void main() {
// Create CAlpha class
CRuntimeClass *pRTCAlpha = RUNTIME_CLASS(CAlpha);
CObject *pObj1;
pObj1 = pRTCAlpha->CreateObject();
printf("CAlpha class=%s\n", pObj1->GetRuntimeClass()->m_lpszClassName);
// Create CBeta class
CRuntimeClass* pRTCBeta = RUNTIME_CLASS(CBeta);
CObject *pObj2;
pObj2 = pRTCBeta->CreateObject();
printf("CBeta class=%s\n", pObj2->GetRuntimeClass()->m_lpszClassName);
delete pObj1;
delete pObj2;
}
