RTTI에 대한 이해
이번에는 C++의 RTTI (Runtime Type Indentification)
에 대해 소개하고자 합니다.
다형성(Polymorphism)을 갖는 C++ class 들(하나 이상의 가상함수를 포함)에 대해 적용 가능합니다.
클래스 다형성은 가상함수에 의해 구현되므로, RTTI는 가상함수를 갖는 클래스의 상속관계에서만 사용가능합니다.
아마도 이 주제를 찾아 보는 사용자라면 알고 있겠지만, C++ 포인터와 클래스 상속에 대한 이해가 요구됩니다.
왜 RTTI 가 필요한지 코드를 통해 살펴보겠습니다.
상속관계를 갖는 3개의 클래스 CWnd, CView, CTreeView 가 존재합니다.
MFC의 클래스와 이름만 같을 뿐 별 의미없는 단순 클래스 입니다.
  
    
부모 클래스 포인터로 아래 클래스의 객체들을 생성합니다.
부모 클래스 포인터는 자식 객체를 가르킬 수 있으므로, 현재까지 아무 문제도 없습니다.
이제 형변환을 해보겠습니다.
첫번째는 CtreeView 형 포인터가 CTreeView형 객체를 지시하게 되므로 안전(O)
두번째는 CWnd형 객체의 주소를 자식(CTreeView) 클래스 포인터로 대입되므로 안전(X)
세번째는 CTreeView 형 포인터가 부모(CView) 클래스 포인터로 대입되므로 안전(O)
두번째 형변환은 문제를 야기하지만, 컴파일 에러를 유발하지 않습니다.
p2 포인터는 실제 부모 클래스(CWnd)를 가르키는 포인터이기 때문에, CTreeView의 m_treeview라는 변수를 갖고 있지 않기 때문입니다.
dynamic_cast<클래스*>(포인터) 로 형변환을 시도합니다.
이 연산자는 포인터가 지시하는 객체의 클래스 타입이 무엇인지 알려주진 않지만, 형변환이 안전한지 체크해 *p22의 경우 null 을 리턴해 형변환이 안전하지 않다고 알려줍니다.
위 코드의 수행 결과는 아래와 같습니다.
p22의 포인터가 null 이므로 출력되지 않습니다.
즉 부모객체를 가리키는 포인터를 자식객체 타입의 포인터에 대입되지 않도록 합니다.
typeid(*p2).name() 은 클래스의 타입을 문자열로 리턴하며 결과는 아래와 같습니다.
감사합니다.
다형성(Polymorphism)을 갖는 C++ class 들(하나 이상의 가상함수를 포함)에 대해 적용 가능합니다.
클래스 다형성은 가상함수에 의해 구현되므로, RTTI는 가상함수를 갖는 클래스의 상속관계에서만 사용가능합니다.
아마도 이 주제를 찾아 보는 사용자라면 알고 있겠지만, C++ 포인터와 클래스 상속에 대한 이해가 요구됩니다.
RTTI
- 런타임에 상속 관계를 갖는 클래스들의 객체 타입을 체크하는 메커니즘.
- dynamic_cast or typeid 연산자로 구성.
- 가상함수(Virtual Fucntion)를 가지는 클래스들만 사용 가능.
왜 RTTI 가 필요한지 코드를 통해 살펴보겠습니다.
안전하지 않은 타입 캐스팅
#include <iostream>
using namespace std;
class CWnd
{
    virtual void draw() { cout << "CWnd" << endl; }
};
class CView:public CWnd
{
public:
    int m_view;
    void draw() { cout << "View" << endl; }        
};
class CTreeView :public CView
{
public:
    int m_treeview;
    void draw() { cout << "Tree View" << endl; }
};
int main()
{
    // 부모클래스 포인터는 자식 클래스 객체를 가르킬 수 있다.
    CWnd *pw = new CWnd;
    CWnd *pv = new CView;
    CWnd *pt = new CTreeView;
    // 느슨한 형변환
    CTreeView    *p1 = (CTreeView*)pt; // (안전 O)
    CTreeView    *p2 = (CTreeView*)pw; // (안전 X)
    CView        *p3 = (CTreeView*)pt; // (안전 O)
    // pw는 자식클래스의 변수가 없음(안전 X)
    p2->m_treeview = 0;    
    delete pw;
    delete pv;
    delete pt;
}
상속관계를 갖는 3개의 클래스 CWnd, CView, CTreeView 가 존재합니다.
MFC의 클래스와 이름만 같을 뿐 별 의미없는 단순 클래스 입니다.
|   | 
| [예제 클래스 상속구조] | 
부모 클래스 포인터로 아래 클래스의 객체들을 생성합니다.
CWnd *pw = new CWnd; CWnd *pv = new CView; CWnd *pt = new CTreeView;
부모 클래스 포인터는 자식 객체를 가르킬 수 있으므로, 현재까지 아무 문제도 없습니다.
이제 형변환을 해보겠습니다.
CTreeView *p1 = (CTreeView*)pt; // (안전 O) CTreeView *p2 = (CTreeView*)pw; // (안전 X) CView *p3 = (CTreeView*)pt; // (안전 O)
첫번째는 CtreeView 형 포인터가 CTreeView형 객체를 지시하게 되므로 안전(O)
두번째는 CWnd형 객체의 주소를 자식(CTreeView) 클래스 포인터로 대입되므로 안전(X)
세번째는 CTreeView 형 포인터가 부모(CView) 클래스 포인터로 대입되므로 안전(O)
두번째 형변환은 문제를 야기하지만, 컴파일 에러를 유발하지 않습니다.
p2->m_treeview = 0;위 코드는 런타임에 문제를 발생시킵니다.
p2 포인터는 실제 부모 클래스(CWnd)를 가르키는 포인터이기 때문에, CTreeView의 m_treeview라는 변수를 갖고 있지 않기 때문입니다.
해결방안 1 (dynamic_cast 연산자)
int main()
{
    // 부모클래스 포인터는 자식 클래스 객체를 가르킬 수 있다.
    CWnd *pw = new CWnd;
    CWnd *pv = new CView;
    CWnd *pt = new CTreeView;
    // RTTI(Runtime Type Identification) Case 1
    CTreeView    *p11 = dynamic_cast<CTreeView*>(pt);
    CTreeView    *p22 = dynamic_cast<CTreeView*>(pw);
    CView        *p33 = dynamic_cast<CTreeView*>(pt);
    if (p11)
        cout << "p11 is safe" << endl;
    if (p22)
        cout << "p22 is save" << endl;
    if (p33)
        cout << "p33 is safe" << endl;
    if(pw)
        delete pw;
    if(pv)
        delete pv;
    if(pt)
        delete pt;
}
dynamic_cast<클래스*>(포인터) 로 형변환을 시도합니다.
이 연산자는 포인터가 지시하는 객체의 클래스 타입이 무엇인지 알려주진 않지만, 형변환이 안전한지 체크해 *p22의 경우 null 을 리턴해 형변환이 안전하지 않다고 알려줍니다.
위 코드의 수행 결과는 아래와 같습니다.
p22의 포인터가 null 이므로 출력되지 않습니다.
즉 부모객체를 가리키는 포인터를 자식객체 타입의 포인터에 대입되지 않도록 합니다.
해결방안 2 (typeid 연산자)
#include <typeinfo>
int main()
{
    // 부모클래스 포인터는 자식 클래스 객체를 가르킬 수 있다.
    CWnd *pw = new CWnd;
    CWnd *pv = new CView;
    CWnd *pt = new CTreeView;
    // 느슨한 형변환
    CTreeView    *p1 = (CTreeView*)pt; // (안전 O)
    CTreeView    *p2 = (CTreeView*)pw; // (안전 X)
    CView        *p3 = (CTreeView*)pt; // (안전 O)
    // pw는 자식클래스의 변수가 없음(체크 후 사용)
    if (typeid(CTreeView) == typeid(*p2))
        p2->m_treeview = 0;
    else
        cout << typeid(*p2).name() << endl;
    delete pw;
    delete pv;
    delete pt;
}
typeid 연산자를 이용해 포인터가 가르키는 곳의 타입(*p2)이 CTreeView와 일치하는지
판단해 형변환을 시도합니다.typeid(*p2).name() 은 클래스의 타입을 문자열로 리턴하며 결과는 아래와 같습니다.
감사합니다.


 
 
또 하나 알아갑니다 고맙습니다
답글삭제