항목 27. 캐스팅은 절약, 또 절약! 잊지 말자
1. 캐스트 방법
(1) C스타일의 캐스트 -> 구형스타일
1) (T) 표현식
2) T(표현식) : 함수방식의 캐스트
(2) C++스타일의 캐스팅
1) const_cast - 객체의 상수성을 없앰
2) dynamic_cast - 다운캐스팅, 런타임 비용이 크다.
3) reinterpret_cast
4) static_cast - 일반적인 캐스트, 포인터타입은 상속관계에 있는경우만 변환가능하다.
* 참고 - 대부분 개발자들은, 캐스팅이 컴파일러에게 알려주는용도만으로 알고 있지만, 실상은 그렇지않다.
타입변환시 런타임에 실행되는 코드가 꽤 있다. 즉 런타임 비용이 든다는것이다.
2. 캐스팅 이슈
(1) 런타임 비용 발생 예제
class Base{...};
class Derived:Base{...};
Derived d;
Base *pb = &d; // Derived* => Base*의 암시적 변환이 이루어진다.
Derived *pd = &d; // pb와 pd가 같은 d를 가리키지만, 주소값은 다르게 표현된다는 것이다.
위코드를 해석하면, 4번째 줄에서 포인터의 offset을 derived*에 적용하여, 실제 base*의 포인터값을 구하는 동작이 런타임에 동작한다. --> 런타임 비용 발생.
객체하나가 가질 수 있는 주소가 오직 한 개가 아니라는 것을 이해해야한다. -> 즉, 타입변환으로 미정의 동작을 낳을 수 있다.
(2) 객체 사본이 발생하는 예제
기본클래스의 가상함수를 파생클래스객체에서 재정의할때 기본클래스 버전을 호출하는 요구사항을 받았다고 가정해보자.
class Window{
public:
virtual void onResize() {};
};
class SpecialWindow : public Window {
public:
virtual void onResize() {
static_cast<Window>(*this).onResize(); // error!
… // special Window 만의 작업을 수행하는 부분
}
};
* 풀이 - static_cast과정에서 사본이 발생하고 사본의 가상함수를 호출하므로. 변경하고자 하는 객체는 아무런동작하지 않을 것이다.
* 해결법 - 캐스팅을 빼자. *this를 기본클래스 객체로 취급하려고 들지마라. 그냥 기본클래스의 해당함수를 호출하자.
class SpecialWindow : public Window {
public:
virtual void onResize() {
Window::onResize();
…
}
};
3. dynamic cast ?
- 위에서 앞서 런타임 비용이 많이 든다고 하였다. 실제로 이 연산자는 정말 느리게 구현되어 있다.
* 언제쓰고싶나? - 기본클래스의 포인터를 이용해 파생클래스의 함수를 호출하고 싶을때.. 이때 dynamic_cast의 문제를 피해가는 방법은 2가지가 있다.
(1) 파생 클래스 객체에 대한 포인터를 컨테이너에 담음으로서, 각 객체를 기본클래스의 인터페이스로 조작할 필요를 없애는 방법이다.(스마트포인터 항목 13 참조)
class Window{};
class SpecialWindow: public Window{
public:
void blink();
};
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
VPW winPtrs;
...
for(VPW::interator iter = Winptr.begin(); iter != Winptr.end(); iter++){
if(SpecialWindow *psw = dynamic_cast<SpecialWindow*> (iter->get()) // dynamic cast 발생
{ psw -> blink(); }
}
수정해보자.
typedef std::vector<std::tr1::shared_ptr<SpecialWindow>> VPSW;
VPSW winPtrs;
…
for(VPSW::iterator iter = Winptr.begin(); iter != Winptr.end(); iter++){
(*iter)-> blink(); } // dynamic_cast를 쓸필요가 없다.
}
* 하지만 이코드는 Window에서 파생되는 모든 녀석들에 대한 포인터를 똑같은 컨테이너에 저장할 수 없다.
(2) 2번째 방법으로는 기본클래스에서 아무기능도 없는 blink()를 구현하여 가상함수로 제공하는 방법이다.
이것만은 잊지 말자!
1. 다른 방법이 가능하다면, 캐스팅은 피하자. 특히 dynamic_cast는 더더욱 피해야 한다.
2. 캐스팅이 불가피하다면, 함수안에 숨겨보자. 그럼 캐스팅없이 이함수를 호출할 수 있을 것이다.
3. 구형 스타일말구 C++스타일의 캐스트를 선호하자.