오늘 소개할 내용은 RTTI로
Run Time Type Information
즉, 런타임에 객체의 타입을 확인하는 C++의 매커니즘이다.
C#에 Type 타입이 있다면 C++에는 std::type_info 타입이 있다.
Type과 std::type_info의 차이가 있다면
Type은 컴파일 타임과 런타임 둘 다 확인할 수 있지만
(컴파일 타임에는 클래스의 원형 타입이고 런타임에는 인스턴스의 타입으로 결정된다.)
std::type_info는 런타임에만 확인이 가능하다.
또 std::type_info의 기능이 좀 더 적은 편이다.
std::type_info는 객체의 원형 타입을 반환한다. 여러 캐스팅을 거친다고 해도 항상 그 객체가 초기에 선언된 타입만을 가리킨다.
std::type_info 클래스를 사용하기 위해서는 헤더에
#include <typeinfo>
를 선언해야 한다.
RTTI
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual void print() {
std::cout << "Base\n";
}
};
class Derived : public Base {
public:
virtual void print() {
std::cout << "Derived\n";
}
};
int main() {
Base B;
Derived D;
Base* pB = static_cast<Base*>(&D);
Derived* pD = static_cast<Derived*>(&B);
const std::type_info& infoB = typeid(pB);
const std::type_info& infoD = typeid(pD);
pB->print();
std::cout << infoB.name() << std::endl;
pD->print();
std::cout << infoD.name() << std::endl;
return 0;
}
typeid 는 런타임에 개체의 형식을 확인하는 키워드이다.
반환타입은 const type_info& 이다.
static_cast는 논리적으로 변환이 가능한 모든 것에 대해 컴파일러가 허용(방관에 가깝다)하는 형변환이다.
Base* pB = static_cast<Base*>(&D);
의 경우 업캐스팅이므로 문제가 되지 않지만
Derived* pD = static_cast<Derived*>(&B);
의 경우 다운캐스팅이므로 만약 자식 객체에만 존재하는 멤버를 호출하는 경우 오류가 발생할 수 있다.
하지만 컴파일러는 이를 무시하고 진행한다.
위 코드를 실행해보면
<출력>
Derived
class Base * __ptr64
Base
class Derived * __ptr64
pB는 업캐스팅된 포인터이며 자식 객체를 가리키지만
선언된 타입은 Base* 타입이므로 std::type_info에서는 Base* 타입으로 나타난다. (__ptr64는 64bit 운영체제에서의 네이티브 포인터 타입을 말한다.)
반면 pD는 다운캐스팅된 포인터이며 부모 객체를 가리키지만
선언된 타입은 Derived* 타입이므로 std::type_info에서는 Derived* 타입으로 나타난다.
따라서 std::type_info는 객체의 원형 타입을 가리키는 것임을 알 수 있다.
Dynamic Cast
C++에는 여러가지 캐스팅이 존재한다.
static_cast
dynamic_cast
const_cast
reinterpret_cast
그 중 dynamic_cast는 상속관계의 객체에 한하여 안전한 캐스팅을 하기 위해 사용한다.
그리고 이 dynamic_cast는 RTTI와 뗄 수 없는 친구사이이다.
dynamic_cast를 위한 조건은 다음과 같다.
- 서로 상속 관계에 놓여있는 클래스여야 한다.
- 상속 관계에 있는 클래스들에 가상 함수가 존재해야 한다. (다형성)
dynamic_cast를 사용하기 위해서는 반드시 virtual 함수가 존재해야 한다.
일반적으로 업캐스팅을 하는 경우에는 아무런 문제가 없다. 부모 객체에서 직접 호출되는 멤버를 자식 객체가 모두 가지고 있기 때문이다.
따라서 dynamic_cast로 업캐스팅을 하는 경우에는 정상적으로 처리된다.
하지만 다운캐스팅을 하는 경우에는 문제가 발생할 수 있다.
캐스팅될 대상의 원형이 부모인 경우에는 자식 객체에서 직접 호출되는 멤버 일부를 부모 객체가 가지고 있지 않을 수 있기 때문에 문제가 발생한다.
하지만 캐스팅될 대상의 원형이 자식인 경우에는, 즉 자식 객체가 부모 객체로 캐스팅되었다가 다시 자식 객체로 캐스팅된 경우에는 문제가 발생하지 않는다.
따라서 dynamic_cast로 다운캐스팅을 하는 경우에는 안전한 캐스팅을 보장하기 위해 캐스팅이 불가능한 경우에는 nullptr를 반환한다.
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual void print() {
std::cout << "Base\n";
}
};
class Derived : public Base {
public:
virtual void print() {
std::cout << "Derived\n";
}
};
int main() {
Base B;
Derived D;
Base* pB = dynamic_cast<Base*>(&D);
Derived* pD = dynamic_cast<Derived*>(&B);
Derived* ppD = dynamic_cast<Derived*>(pB);
const std::type_info& infoB = typeid(pB);
const std::type_info& infoD = typeid(pD);
const std::type_info& infoPD = typeid(ppD);
std::cout << "*** pB ***\n";
pB->print();
std::cout << infoB.name() << "\n\n";
std::cout << "*** pD ***\n";
// pD->print(); // pD is nullptr
if (pD == nullptr) std::cout << "pD is nullptr\n";
std::cout << infoD.name() << "\n\n";
std::cout << "*** ppD ***\n";
ppD->print();
std::cout << infoPD.name() << "\n\n";
return 0;
}
<출력>
*** pB ***
Derived
class Base * __ptr64
*** pD ***
pD is nullptr
class Derived * __ptr64
*** ppD ***
Derived
class Derived * __ptr64
pB <- D (업캐스팅, 정상)
pD <- B (다운캐스팅, 비정상)
ppD <- pB (다운캐스팅, 정상)
static_cast의 경우 위의 세 형변환을 모두 정상으로 처리하지만
dynamic_cast는 부모 객체를 자식 객체로 다운캐스팅하는 형변환을 비정상으로 처리하여 nullptr를 반환한다.
RTTI에서도 ppD의 원형 타입이 Derived로 나오는 것을 보아
안전한 다운캐스팅이란 std::type_info의 타입과 인스턴스의 타입이 일치하는 경우라고 결론지을 수 있다.
(위 코드에서는 인스턴스의 타입을 확인하기 위해 print 함수를 사용했다.)
결론
실제로 RTTI를 이 객체가 dynamic_cast를 할 수 있는지 판별하는데 사용한다.
위의 예시에서는 부모와 자식 관계에 놓인 두 클래스에 대해서만 판별하기 때문에 굳이 RTTI가 필요한가? 싶지만
현실 코드에는 이렇게 간단 명료하게 파악하기가 매우 힘들다.
부모와 자식 외에도 삼촌과 이모가 존재할 수 있고, 꼬리에 꼬리를 물어 상하 관계를 파악하기가 힘든 클래스가 대부분이다. 그래서 RTTI를 통해 상하 관계를 파악하지 않고 곧바로 dynamic_cast를 사용할 수 있는 것이다.
'Programming Languages > C++ Study' 카테고리의 다른 글
C++ MultiThread) std::atomic과 SpinLock (4) | 2024.03.06 |
---|---|
C++) 범위 기반 for문(Range based for loop)과 사용 조건 (0) | 2024.01.26 |
C++) *(Asterisk, 별표) 연산자 활용하기 (0) | 2024.01.24 |
C++) _CrtIsValidHeapPointer(block) (0) | 2024.01.22 |