책 정리/Effective C++ 3rd

항목 27. 캐스팅은 절약, 또 절약! 잊지 말자

ocean20 2020. 2. 3. 16:04

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++스타일의 캐스트를 선호하자.