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() 은 클래스의 타입을 문자열로 리턴하며 결과는 아래와 같습니다.
감사합니다.
또 하나 알아갑니다 고맙습니다
답글삭제