[C++] RTTI | 실행 시간 데이터형 정보
RTTI (Runtime Type Identification)
실행 시간 데이터형 정보
- 많은 클래스 라이브러리들이 실행 시간 중 데이터형을 결정하는 방법을 제각기로 제공하기 때문에
이들이 서로 호환되도록 하기 위해서는 RTTI를 위한 언어 표준이 필요하다.
C++에서의 RTTI 요소
1. dynamic_cast
연산자
dynamic_cast<Type *>(pt)
// 포인터 pt를 Type * 형으로 변환하여 리턴한다.
// (*pt)가 Type형이거나, Type형으로부터 직/간접적으로 파생된 객체이면, pt를 Type *형 포인터로 변환을 허용한다.
// 변환할 수 없다면, 0을 리턴한다.
if (pt1 = dynamic_cast<exClass *>(pt2))
pt1->exMethod();
// pt2 포인터를 exClass * 형으로 변환 가능하다면,
// 변환하여 pt1에 대입하고,
// pt1객체의 exMethod() 메서드를 실행한다.
- 기초 클래스형을 지시하는 포인터로부터 파생 클래스형을 지시하는 포인터를 생성하며,
이것이 불가능한 경우엔 0을 리턴한다.
- 포인터가 지시하는 객체형을 알려주지는 않는 대신,
해당 객체의 주소를 특정형의 포인터에 대입 가능한지에 대한 여부를 알려 준다.
* 참조형에 대한 dynamic_cast
연산자
- NULL
값에 해당되는 참조값은 존재하지 않기 때문에,
참조형에 대한 타입 캐스팅이 실패하면 bad_cast
형 예외를 발생시킨다.
- bad_cast
형 예외는 exception
클래스로부터 파생되었으며, typeinfo
헤더파일에 선언되어 있다.
#include <typeinfo> // for bad_cast
...
try {
Superb & rs = dynamic_cast<Superb &>(rg);
...
}
catch(bad_cast &){
...
};
2. typeid
연산자
- 객체의 정확한 데이터형을 나타내는 값을 리턴한다.
- typeid
연산자는 type_info
객체에 대한 참조를 리턴한다.
- typeid
연산자에 요구되는 매개변수는 "클래스 이름" 또는 "객체로 평가되는 식"이다.
typeid(className)
// 또는
typeid(object)
※ typeid
연산자의 매개변수가 NULL
이면, bad_typeid
예외를 발생시킨다.
- bad_typeid
는 exception
클래스로부터 파생되었으며, typeinfo
헤더 파일에 선언되어 있다.
* typeid(*pt).name()
메서드
- 포인터 pt
가 가리키고 있는 객체의 클래스 이름을 문자열로 리턴한다.
* type_info
- 어떤 특별한 데이터형에 대한 정보를 저장한 구조체이다.
- type_info
클래스는 typeinfo
헤더파일 (typeinfo.h
)에 선언되어 있다.
- 데이터형에 대한 비교를 위해 ==
연산자와 !=
연산자가 이에 맞춰서 오버로딩 되어있다.
typeid(Magnificent) == typeid(*pg)
// 비교 연산자가 오버로딩 되어있기에 가능한 식
※ RTTI는 Virtual Function을 가진 클래스에만 사용할 수 있다.
- 파생 객체들의 주소를 기초 클래스 포인터에 대입하는 작업은
상속 계층구조를 가진 클래스의 가상 함수에서만 필요하기 때문이다.
Usage RTTI (RTTI 사용예시)
// rtti1.cpp -- using the RTTI dynamic_cast operator
#include <iostream>
#include <cstdlib>
#include <ctime>
using std::cout;
class Grand {
private:
int hold;
public:
Grand(int h = 0) : hold(h) {}
virtual void Speak() const { cout << "I am a grand class!\n";}
virtual int Value() const { return hold; }
};
class Superb : public Grand {
public:
Superb(int h = 0) : Grand(h) {}
void Speak() const {cout << "I am a superb class!!\n"; }
virtual void Say() const { cout << "I hold the superb value of " << Value() << "!\n";}
};
class Magnificent : public Superb {
private:
char ch;
public:
Magnificent(int h = 0, char c = 'A') : Superb(h), ch(c) {}
void Speak() const {cout << "I am a magnificent class!!!\n";}
void Say() const {cout << "I hold the character " << ch << " and the integer " << Value() << "!\n"; }
};
Grand * GetOne() { // generate one of three kinds of objects randomly
Grand * p;
switch( std::rand() % 3) {
case 0: p = new Grand(std::rand() % 100);
break;
case 1: p = new Superb(std::rand() % 100);
break;
case 2: p = new Magnificent(std::rand() % 100, 'A' + std::rand() % 26);
break;
}
return p;
}
int main() {
std::srand(std::time(0));
Grand * pg;
Superb * ps;
for (int i = 0; i < 5; i++) {
pg = GetOne();
pg->Speak();
if( ps = dynamic_cast<Superb *>(pg))
ps->Say();
}
return 0;
}
// main() 함수의 내용을 typeid 만으로 구현하는 방법
Grand * pg;
Superb * ps;
Magnificent * pm;
for (int i = 0; i < 5; i++) {
pg = GetOne();
if (typeid(Magnificent) == typeid(*pg)) {
pm = (Magnificent *) pg;
pm->Speak();
pm->Say();
}
else if (typeid(Superb) == typeid(*pg)) {
ps = (Superb *) pg;
ps->Speak();
ps->Say();
}
else
pg->Speak();
}