즉, static_cast의 경우는 자식클래스 포인터에서 부모의 인스턴스를 받아도 경고없이 그냥 허용한다는거다. 아예 다른 타입의 경우 변환을 불허하지만, 상속관계에 있는 클래스의 경우에는 C스타일의 캐스트처럼 그냥 다 허용한다는 것. 결국 부모클래스의 인스턴스로 차일드 클래스의 멤버를 호출하면 문제가 발생하게 되는 것이다.
그럼 static_cast를 아예 안쓰면 안전하지 않냐고? 인스턴스 관리에 있어서 항상 최하위 클래스의 포인터로만 작업하기 어렵다는 것은 경험상 알고 있을 것이다. (사과, 복숭아, 오이, 마늘 등을 모아놓고, 한큐에 "껍질까기"메서드를 호출하고 싶을 때처럼..)
과거에 상속이 얽히고 설킨 상황에서 포인터로 멤버 함수를 호출하고자 했을 때 함수 선언을 virtual로 하여 런타임에 인스턴스 종류를 파악하게 하여 메서드 호출에서의 폴트를 피했던 것처럼, 캐스팅과 관련해서도 유사한 방법이 필요하다. 캐스팅 연산자에서는 dynamic_cast가 그런 역할을 하고 있어서, 상속관계에 있는 클래스의 포인터간에 안전한 타입변환을 해주고, 문제의 소지가 있는 형변환에 대해서는 null을 전달하여 오동작을 방지하게 해준다. dynamic_cast에 대해서는 다음 시간에..
아 그럼 여기서 또 질문이 나올 수 있는데. dynamic_cast로 안전하게 상속 클래스간 포인터변환을 할 수 있다면, static_cast가 굳이 상속관계 클래스 포인터간 캐스팅을 지원하는 이유가 뭐냐? 그냥 지원 안해버리면 되는데.... 맞는 말이다.
이것은 virtual 함수의 특성과 비슷한 답변을 하면 될 것 같다. virtual 함수를 사용하게 되면, 실시간 타입검사(RTTI)가 들어가기 때문에 아무래도 속도상 손해가 있다. 결국 퍼포먼스가 무지막지하게 중요한 소프트웨어라서 RTTI를 빼버리고 컴파일하고 싶거나, 컴파일러가 특이해서 RTTI옵션을 아예 지원하지 않는다면 virtual 함수를 쓸 수 없듯이, dynamic_cast역시 RTTI를 쓸 수 없는 환경에서는 사용할 수가 없다. 이것은 dynamic_cast를 소개할 때 한 번 더 언급하게 될 것 같다. 아무튼 이런 상황에서 만능열쇠와도 같은 (=위험하기 짝이 없는) C스타일 캐스팅보다는 그나마 static_cast를 사용해주는게 차선책이 될 수 있음을 이해할 수 있을 것이다. 이쯤되면 왜 한놈은 스태틱이고 한놈은 다이내믹이라는 이름을 갖고 있는지도 충분히 이해가 될 듯.
한 가지 재미있는 것은, static_cast의 경우 기본적으로 상속관계가 아닌 클래스끼리는 포인터변환을 할 수 없지만, 중간에 void* 로 한 번 변환을 해두면 자유롭게 캐스팅이 가능하다는 것.
class CClass1
{
public:
int m_intTest;
};
class CClass2
{
public:
int m_intTest2;
int m_intAdditional;
};
void main()
{
CClass1 *pcl1;
CClass2 *pcl2;
void *ptemp;
pcl1 = static_cast<CClass1*>(pcl2); // ERROR
ptemp = static_cast<void*>(pcl2); // OK
pcl1 = static_cast<CClass1*>(ptemp); // OK
}
나의 미천한 실력으로 정확한 이유는 알 수 없으나, 모든 클래스를 void를 상속한 걸로 간주하기 때문에 그런것이 아닐까 추측한다. 아니면, 파일 처리나 통신 등에서 serialize를 지원하게 하기 위해서일지도 모르고.. 또는, malloc등의 함수를 통해 고정된 타입으로 메모리주소를 넘겨받는 경우가 있어서 그런지도 모르겠다.
암튼 이렇게까지 할거면 걍 C캐스팅 쓰는거나 무슨 차이냐 싶을 수도 있지만, 명시적으로 강제 캐스팅이 불가피하다는 것을 보여주기 위해선 나쁘지 않을것 같기도 하다.
사실 여기서 소개한 것 외에도 몇 가지 특성이 더 있기는 한데.. 나중에 생각나면 추가해 보도록 하겠다.
다음 시간에는 dynamic_cast에 대해서 살펴볼 것이다.
그리고 끝으로 언급해야 될 것이 한 가지 있다면.. C++에는 오늘 살펴본 static_cast처럼 C++특유의 네 가지 정도의 캐스팅 연산자가 있으며, 앞서 대강 살펴봤듯이 각각의 역할이 뚜렷이 구분되어 있다. 이것은 프로그래머에게 실수를 방지하도록 돕는 것과 동시에, 타인이 소스를 읽어봤을 때 프로그래머의 의중을 쉽게 파악할 수 있도록 하는 역할도 겸한다. 그런데 이런 상황에서 갑자기 중간에 C스타일의 캐스팅이 툭 튀어나온다면? (워3 용어로, 갑툭튀라고 할 수 있겠다) 그동안 쌓아놓은 무결성과 가독성이 와르르 무너지게 되는 것이다. 일반 타입 변환에 static_cast를 쓰라고까지는 못하겠지만(그러나 실제로 이렇게 프로그래밍하는 사람도 많음), 클래스 또는 포인터 변환에서만이라도 C++ 캐스팅 키워드를 반드시 사용해보면 어떨까 싶다. 그리고 이 캐스팅 기법들은 포인터뿐만 아니고 레퍼런스에도 동일한 방식으로 지원을 하고 있으니 레퍼런스 변환에도 적극적으로 사용해보도록 하자.