결론: 사용하지 마세요
Dynamic Cast: RTTI (RunTime Type Information) 을 사용하는 것인데 대부분의 C++ 프로젝트에선 사용을 금지하고 있다.
구글 C++ 스타일 가이드의 RTTI 파트를 보면, Avoid using Run Time Type Information 부분에서
Dynamic Cast를 사용하지 말 것을 주문하고 있다.
이 접근 자체가 클래스 구조를 잘못 짠 것이고,
이런 RTTI가 없는 코드가 유지보수가 쉬운 코드라는 것이다.
https://google.github.io/styleguide/cppguide.html
다만 이 Dynamic Cast 어떻게 동작하는지 알 필요는 있다.
Upcast 업캐스트: Derived 클래스에서 Base 클래스로 변환하는 것
Downcast 다운캐스트: Base클래스에서 Derived 클래스로 내려 변환하는 것
예제) Upcast
현재 프로세스 메모리 뷰
Cat* 가 생성되고 Cat*는 Cat object를 가리키고, 이에대한 모든 스코프를 가지고 있다.
이 때,
upcast를 할 수 있다.
Animal의 포인터를 만들어서 여기에 바로 catPtr, 즉 Cat의 포인터를 넣어주면 된다.
meow, 즉 고양이의 speak() 와 결과값이 같다.
이것이 가능한 이유는,
고양이 object의 virtual table 포인터 *VT는 Cat의 VT를 가리킬 것이고,
Cat*를 Animal*로 upcast 하더라도 이 포인터가 가리키는 object는 고양이 object 그대로이기 때문에
마찬가지로 이 VT를 따라가서 Cat speak() 인 meow를 실행시켜 주는 것이다.
하지만 animalPtr을 통해서 고양이만의 knead() 함수를 호출할 수 없다.
왜냐하면 Animal *는 Animal에 대한 정보만 접근이 가능(스코프)하기 때문에 고양이만의 함수인 knead()는 호출이 불가능한 것.
*위는 implicit한 방법으로 upcast 하였지만 static_cast로 upcast를 할 수도 있다.
예제) DownCast
다운캐스트는 Base클래스에서 Derived 클래스로 내려 변환하는 것이다.
우선 베이스 클래스 포인터가 파생 클래스의 오브젝트를 가리키도록 만들어 보자.
이 형태는 동물 포인터가 고양이 오브젝트를 가리키는 것과 같다.
이 상황에서도 오브젝트는 고양이 오브젝트이기 때문에 VT*를 따라가서 speak() 함수를 호출해 "meow"를 호출할 것이다.
이런 상황에서 고양이만 할 수 있는 knead() 함수를 호출해 주고 싶다면,
현재 포인터는 베이스 클래스의 포인터이기 때문에 호출이 불가능하므로,
아까와는 반대로 다운캐스트 해 주어야 한다.
Cat* catPtr = animalPtr; 처럼 다운캐스트 해주면 될 것 같지만,
에러가 나는 이유는 암묵적인 형변환, implicit downcast를 해 주었기 때문이다.
C++ 컴파일러는 implicit downcast를 허용하지 않는다. 여러 문제가 일어날 수 있기 때문이다.
바로 위에서처럼 고양이 오브젝트를 만들고 고양이로 다운캐스트 해 준 다음 knead()함수를 불러주면 문제가 없지만,
강아지 오브젝트나 동물 오브젝트를 만들어 놓고 고양이로 다운캐스트 해 주게 되면 문제가 발생한다.
이러한 이유 때문에 암묵적 다운캐스트는 허용하지 않는다.
따라서 프로그래머가 직접 static_cast 를 통해서 다운캐스트 해 줄 수 있다.
하지만, 이 방법은 매우 위험한 방법의 캐스팅 방법이다.
방금 언급했듯이 강아지 오브젝트를 만들어놓고
고양이 포인터로 캐스팅 한 다음
고양이만의 함수인 knead() 함수를 부를 수 있다.
이것은 심각한 문제를 불러 일으킬 수도 있다!
만약 Animal* 를 통해서 Animal object를 만들어 놓고 다운캐스팅을 통해서 Cat*로 바꿔놓을 수 있는데,
이렇게 되면 고양이만의 knead() 등의 함수, 데이터에 접근이 가능하게 된다.
문제는 이 고양이 데이터가 animal obj 안에 저장이 되어 있을 텐데 고양이만의 함수가 이 데이터에 접근하게 되면
존재하지 않는 메모리에 접근하게 되므로 프로그램이 터질 수도 있고,
더 심하게는 아주 이상한 행동을 하는 프로그램이 만들어질 수도 있다.
이러한 이유로 static_cast 를 통한 다운캐스트는 굉장히 위험한 방법이다.
이러한 위험한 방법을 방지하기 위해서 dynamic_cast를 사용한다.
Dynamic_Cast
static_cast에서 보았던 위험성을 방지하고 안전하게 캐스팅 하기 위해서 dynamic_cast 를 사용할 수 있다.
다이나믹 캐스트의 특징은,
캐스트하고 싶은 오브젝트의 타입과 포인터의 타입이 같지 않으면 nullptr을 반환해 준다는 것이다.
따라서 if 문을 사용해서 nullptr가 반환될 경우 고양이 오브젝트가 아니라고 출력해 준 후에 프로그램을 종료해 주도록
코드를 만들어 보면,
메세지가 실행되고 종료가 된다.
즉, dynamic_cast를 통해서 nullptr이 반환되었다는 이야기이다.
마찬가지로 Animal 대신 Dog 오브젝트를 만들어도
똑같은 결과가 나온다.
Cat 오브젝트를 만들고 실행해보면 정상적으로 함수들이 실행되는 것을 확인할 수 있다.
dynamic_cast가 런타임 시간에 animalPtr이 가리키는 오브젝트가 진짜 고양이 오브젝트인지 확인하고 포인터를 반환해 준 것이다.
어떤 방식으로 구동하는가?
: 다이나믹 캐스트는 typeinfo 기능을 통해서 구현된다.
type_info 각 클래스는 각각의 type information class를 가지고 있다.
https://en.cppreference.com/w/cpp/types/type_info
type info는 typeid() 를 통해서 호출할 수 있다.
각 클래스의 typeid()의 이름을 출력해 보자.
이렇게 dynamic_cast를 통해서 실제 오브젝트와 캐스팅하고 싶은 클래스타입이 일치하는지 런타임에 알아내고 있다.
각 Virtual Table마다 그 타입에 맞는 type information에 대한 포인터가 저장이 되어 있다. (중요!)
Animal, Cat, Dog의 VT에는 각각 타입에 맞는 정보가 저장되어 있다고 생각하면 된다.
예제에서와 같이 Animal*가 Animal 오브젝트를 가리키고 있을 때 다이나믹 캐스트을 통해서 Cat*로 바꿔 주었을 때,
Animal 오브젝트의 VT*가 가리키고 있는 Animal VT안의 type info와 캐스팅하고자 하는 Cat 오브젝트의 VT*가 가리키고 있는 Cat VT안의 type info가 맞지 않으므로 null ptr을 반환해 주게 되는 것이다.
마찬가지로 Animal* 에 Dog 오브젝트를 만들어 놓고
고양이로 캐스트 시도 하면 이 VT*들이 가리키는 type info들이 다르기 때문에 null ptr을 반환해 준다.
Animal*에 Cat 오브젝트를 만들고 고양이로 캐스트 하게 되면,
이 두 타입이 맞기 때문에 제대로 된 포인터를 반환해 준다.
글에서는 간단하게 typeinfo를 체크했다고 이야기 했지만
실제로는 type 체크를 할 때 class 구조를 전부 파악해서 이 두개의 타입이 꼭 정확히 일치하지 않더라도
안전하게 캐스팅이 가능하다면 캐스팅 해 준다.
더 자세한 정보는
cppreference 참고할 것.
https://en.cppreference.com/w/cpp/language/dynamic_cast
'모던C++ > 상속관계 Inheritance' 카테고리의 다른 글
7. Virtual Inheritance 가상 상속_C++ (0) | 2022.09.01 |
---|---|
6. 다중 상속 multiple inheritance _ C++ (0) | 2022.09.01 |
5. Pure Virtual Function 순수 가상 함수_ C++ (0) | 2022.09.01 |
4. Virtual Table, Virtual Function _ C++ (0) | 2022.08.31 |
3. 가상 함수 Virtual Function _ C++ / Dynamic Polymorphism (0) | 2022.08.23 |
2. 접근 권한 키워드 - public, private, protected, 파생 클래스 _ C++ (0) | 2022.08.23 |
1. Inheritance 상속이란? _ C++ (0) | 2022.08.23 |