앞서 C++의 캐스팅에 대해 알아보며, dynamic_cast 부분에서 'RTTI'를 통해 런타임에 타입을 알아낸다고 작성했다. RTTI란 과연 무엇이고, 구체적으로 어떤 기능들을 지원하는지 알아보자.
RTTI는 'Run Time Type Information', 또는 'Run Time Type Identification'의 약자로, 번역하면 '런타임 타입 정보' 또는 '런타임 타입 식별'이다. 이는 C++ 언어에서 제공하는 매커니즘으로, 런타임, 즉 프로그램 실행 중에 객체의 타입 정보를 식별할 수 있는 기능이다. 원래는 C++ 언어에서 제공하지 않고, 다른 라이브러리들에서 자체적으로 구현하자 이 기능을 넣었다고 한다.
RTTI는 다형성이 있는 클래스에만 사용할 수 있다. 내부의 vftable(virtual function table)에 객체의 타입 정보가 저장되기 때문이다. C++에서 다형성이 있다는 것은, 클래스에 가상 함수(virtual function)이 하나 이상 들어있다는 것이다. 저번 dynamic_cast에 대해서 작성할 때도 이와 같은 조건을 내세웠었다. 이는 RTTI의 조건이기 때문이다. 하지만 다형성을 가지는 기본 클래스는 반드시 '가상 소멸자'를 가져야 하니, 사실 다형성을 가지는 클래스라면 이 조건을 거스를 리 없다.
RTTI는 C++의 세 가지 요소에서 사용되는데, typeid, type_info, 그리고 dynamic_cast 연산자이다. 각각 알아보자
typeid는 런타임에 객체의 타입을 확인하는 연산자이다. 아래 예제를 보자.
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual void vvfunc() {}
};
class Derived : public Base {};
using namespace std;
int main() {
Derived* pd = new Derived;
Base* pb = pd;
cout << typeid( pb ).name() << endl; // P4Base
cout << typeid( *pb ).name() << endl; // 7Derived
cout << typeid( pd ).name() << endl; // P7Derived
cout << typeid( *pd ).name() << endl; // 7Derived
delete pd;
}
클래스 Base와 Derived를 이용해 동적 할당을 진행하고, 그에 대한 타입 정보의 name을 출력했다. 출력 결과는 컴파일러마다 다른데, 자주 쓰이는 gcc나 clang 같은 컴파일러에서는 '망가진(mangled)' 이름을 반환하고, MSVC 등은 알아보기 쉽게 'class Derived *' 같은 형식으로 반환한다.
이곳에서는 'mangled'된 이름을 어떤 규칙으로 구성하는지 볼 수 있다. 또한 boost::core::demangle 함수를 통해 'mangled'된 이름을 알아보기 쉽게 변경할 수 있다고 한다.
중요한 것은 기본 클래스 포인터에 파생 클래스 객체가 들어 있어도, 들어 있는 객체를 역참조해서 typeid로 확인 시, 파생 클래스로 인식한다는 것이다.
방금의 코드를 살짝만 바꿔보자.
int main() {
Derived* pd = new Derived;
Base* pb = pd;
if(typeid(*pd) == typeid(*pb)) cout << "Same Type!" << endl;
// "Same Type!" 출력
delete pd;
}
pd와 pb가 가리키는 객체는 타입이 동일하므로, 위의 if문의 조건은 true다.
typeid는 객체뿐만 아니라, 타입명을 넣어도 동일하게 사용 가능하다.
int main() {
Derived* pd = new Derived;
Base* pb = pd;
if(typeid(*pb) == typeid(Derived)) cout << "*pb Is Derived" << endl;
// "*pb Is Derived" 출력
delete pd;
}
type_info는 타입에 대한 정보를 담고 있는 클래스이다. 방금 본 typeid 연산자가 반환하는 것이 바로 이 type_info 클래스의 객체이다. 이 클래스를 사용하기 위해서는 <typeinfo> 헤더를 인클루드해야한다.
type_info 클래스는 복사 생성자, 할당 연산자가 모두 비공개다. 따라서 임의로 이 클래스의 객체를 생성할 수 없다. 생성할 수 있는 유일한 방법은 typeid 연산자를 사용하는 것이다.
위의 예제에서도 보았듯이, type_info 클래스는 ==, != 연산자를 통해 같은지 아닌지 구분할 수 있다.
dynamic_cast는 상속 계층 구조를 따라 포인터/참조자인 표현식을 type-id의 유형에 따라, 캐스팅해주는 연산자이다. 여기서 상속 계층 구조는 위, 아래, 옆 모두를 의미한다. RTTI를 이용해 캐스팅을 시도해보고, 변환이 불가능하다면 nullptr를 반환한다.
관련 내용은 이미 정리했으므로, 간단히 넘어가겠다.