C/C++ | Posted by Rhio.kim 2007/02/13 22:53

RTTI의 이해

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;
}

'C/C++' 카테고리의 다른 글

RTTI의 이해  (0) 2007/02/13