RTTI 구현

1. 가장 간단한 방식

잠깐 define 선언시 #, ##  사용에 대해서 알아보자

# 선언은?
변수로 넘어온 값을 문자열로 바꾼다.

#define  XSTR(s)    #s
char*  str = XSTR(test)     명령은 아래와 같다.
char*  str = "test"

## 선언은?
변수로 넘어온 값을 변수로 취급한다.

#define VAR_VAR(a, b)   a##b
char*  VAR_VAR(s, tr) = XSTR(test)    명령은 아래와 같다.
char* str

RTTI 구현을 문자열을 이용해서 구현한 방법이다.
문자열을 사용하는 방법은 구현은 간단하지만, 문자열을 비교 할 때, 약간 느리다는 단점이 있다.
실제 사용은  아래와 같이 선언하고, GetClassName() 메쏘드를 실행하면 각 클래스의 이름을 반환한다.

선언

#define DECLARE_RTTI(name)  \
    public:  \
    virtual const char * GetClassName()  const {  return #name; }


정의

class VECTOR
{
    DECLARE_RTTI(VECTOR);


실행

VECTOR  vector;
char* szClassName = vector.GetClassName();    
//클래스 이름을 리턴한다.

 

2. 포인터를 사용하는 방식

문자열 대신 클래스의 정적 변수의 메모리 주소를 이용해 클래스를 비교하면 문자열 방식보다 빠르다.
문자열을 이용 할 때는 매크로를 클래스의 헤더에만 선언하면 되지만, 포인터를 사용하는 방식을
이용하는 경우에는 헤더 파일과 소스 파일에 각각 DECLARE_RTTI와 IMPLEMENT_RTTI 매크를를 포함시켜야 한다.

클래스를 비교하는 경우에는 SAME_RTTI 매크로를 사용한다.

선언

#define DECLARE_RTTI   \
    public: \
    static  const    CRTTI  m_rtti; \
    virtual const    CRTTI & GetRTTI() const    {   return m_rtti; }

#define IMPLEMENT_RTTI(name)        \
     const CRTTI name::m_rtti(#name);

#define SAME_RTTI(a, b)   (&(a).GetRTTI() == &(b).GetRTTI())

//RTTI 클래스
class CRTTI
{
public:
    CRTTI(const std::string &name)  : m_className(name)  {};
    const   std::string & GetClassName()  const  { return m_className; }

private:
    std::string  m_className;
};


정의

//헤더 파일에 추가
class VECTOR
{
    DECLARE_RTTI;

//소스 파일에 추가
IMPLEMENT_RTTI(VECTOR);


실행

    if(SAME_RTTI( lhs, rhs ))
        cout << "같은 클래스" << endl;

 

 

3. 상속 관계 추가

포인터를 이용하는 방식은 어떤 클래스를 사용하는지 알 수는 있지만 상속 관계에 대해서는 알수는 없다.
 포인터를 이용하는 방식에 상속 관계에 대해서 알기 위해 약간 수정하였다

예제 코드는 derived_rtti_test.zip 파일에 있다. 코드가 짧기 때문에 분석이 그렇게 어렵지 않을 것이다.

상속 관계를 고려하지 않을 때는 IMPLEMENT_RTTI 매크로 하나면 m_rtti 초기화가 가능했지만 상속 관계를 나타내기 위해 IMPLEMENTBASE_RTTI 매크로가 추가 되었다.

IMPLEMENTBASE_RTTI : 베이스 클래스일때 사용

IMPLEMENT_RTTI : 상속 받은 클래스 일 경우 사용

상속 적용 이후 소스가 약간 길어져, rtti.h, rtti.cpp 파일로 분리 하였다.

선언

class CCar
{
    DECLARE_RTTI;
public:
    void Show() {   std::cout << "This is Car." << std::endl;  }
};

class CTaxi : CCar
{
    DECLARE_RTTI;
public:
    void Show() {   std::cout << "This is Taxi." << std::endl;  }
};


정의

//CTaxi는 CCar를 상속 받았다. (소스파일에 추가)
IMPLEMENTBASE_RTTI(CCar);
IMPLEMENT_RTTI(CTaxi, CCar);


실행

    CTaxi    taxi;

    if (DERIVED_RTTI(CCar, taxi))
        std::cout << "taxi Derived from CCar" << std::endl;

 

3. 다중 상속의 경우

다중 상속의 경우 부모 클래스가 2개 이상이기 때문에 m_pParentRTTI에 여러개의 포인터를 저장 할 수 있도록 이중포인터(**)로 처리한다.

생성자에 부모클래스의 CRTTI를 여러개 넘기기 위해서. 가변 인자 리스트를 사용한다.
가변 인자는 매크로로 만들기 힘들다. 그래서 직접 입력한다.

추가된 것:

CRTTI(const std::string &name, int numParents, ...);
~CRTTI();

CRTTI::CRTTI(const std::string &name, int numParent, ...) : m_className(name)
{
    if (numParent < 1)
    {
        m_numParent = 0;
        m_pParentRTTI  = NULL;
    }
    else
    {
        m_numParent = numParent;
        m_pParentRTTI = new const CRTTI*[m_numParent];

        va_list v;
        va_start(v, numParent);
        for (int i=0; i < m_numParent; ++i)
        {
            m_pParentRTTI[i] = va_arg(v, const CRTTI*);
        }
        va_end(v);
    }
}

CRTTI::~CRTTI()
{
    delete [] m_pParentRTTI;
}

수정된 것:

2개 이상의 부모 클래스를 저장하기 위해 CRTTI* m_pParentRTTI     ---> CRTTI ** m_pParentRTTI 로 수정   

bool DerivedFrom (const CRTTI & rtti) const

bool    CRTTI::DerivedFrom (const CRTTI & rtti) const
{
    const CRTTI * pCompare = this;
    if (pCompare == &rtti)
        return true; 

    for (int i=0; i < m_numParent; ++i)
    {
        if (m_pParentRTTI[i]->DerivedFrom(rtti))
            return true;
    } 

    return false;
}

 

선언

class CAirplane
{
    DECLARE_RTTI;

public:
    void Show() {  std::cout << "This is Airplane." << std::endl;  }
};

class CTaxi : CCar, CAirplane
{
    DECLARE_RTTI;

public:
    void Show() {   std::cout << "This is Taxi." << std::endl;  }
}


정의

IMPLEMENTBASE_RTTI(CAirplane);
//다중 상속은 매크로를 사용하기 힘들기 때문에 직접 입력한다.
const CRTTI CTaxi::m_rtti ("CTaxi", 2, &CCar::m_rtti, &CAirplane::m_rtti);  
IMPLEMENT_RTTI(CBus, CCar);


실행

    if (DERIVED_RTTI(CCar, taxi))
        std::cout << "taxi Derived from CCar" << std::endl;
    if (DERIVED_RTTI(CAirplane, taxi))
        std::cout << "taxi Derived from CAirplane" << std::endl;

단일 상속용 CRTTI가 더 빠르다. 다중 상속을 꼭 해야 하겠다면 multi_rtti_test.zip 소스를 참고한다.
특별한 경우가 아니면, 단일 상속으로 충분 할것이다.

참고)

정보문화사) 게임 프로그래머를 위한 C++