KoreanFoodie's Study

Effective C++ | 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 본문

Tutorials/C++ : Advanced

Effective C++ | 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자

GoldGiver 2022. 10. 25. 16:18

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

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

핵심 :

1. 다른 방법이 가능하다면 캐스팅은 피하자. 특히 수행 성능에 민감한 코드에서 dynamic_cast 는 몇 번이고 다시 생각하자. 설계 중에 캐스팅이 필요해졌다면, 캐스팅을 쓰지 않는 다른 방법을 시도해 보자.
2. 캐스팅이 어쩔 수 없이 필요하다면, 함수 안에 숨길 수 있도록 해 보자. 이렇게 하면 최소한 사용자는 자신의 코드에 캐스팅을 넣지 않고 이 함수를 호출할 수 있게 된다.
3. 구형 스타일의 캐스트를 쓰려거든 C++ 스타일의 캐스트를 선호하자. 발견하기도 쉽고, 설계자가 어떤 역할을 의도했는지가 더 자세히 드러난다.


구형 스타일의 캐스트는 "(T) expr" , "T (expr)" 처럼 괄호를 쓴다. C++ 에서는 4 가지의 캐스트 방식이 있다 :

  • const_cast : 객체의 상수성(constness)를 없애는 용도로 사용됨
  • dynamic_cast : '안전한 다운캐스팅(safe downcasting)' 을 할 때 사용함 런타임 비용이 높음
  • reinterpret_cast : 포인터를 int 로 바꾸는 등의 하부 수준 캐스팅을 위해 만들어진 연산자로, 적용 결과가 구현환경에 의존적임. 이런 캐스트는 하부 수준 코드 외에는 거의 없어야 함
  • static_cast : 암시적 변환(비상수 객체를 상수 객체로, 혹은 int를 double 로 등)을 강제로 진행할 때 사용. 타입 변환을 거꾸로 수행하는 용도로도 쓰임(void* 를 일반 타입의 포인터로).

구형 스타일의 캐스트를 쓰는 경우는 다음과 같은 경우 말고는 찾아보기 힘들다.

class Widget
{
  public:
    explicit Widget(int size);
};

void doSomeWork(cosnt Widget& w);
// 함수 방식 캐스트를 사용, int 로부터 Widget 생성
doSomeWork(Widget(15));
// C++ 방식 캐스트를 사용, int 로부터 Widget 생성
doSomeWork(static_cast<Widget>(15));


다음 예시를 보자.

class Base { ... };
class Derived: public Base { ... };
Derived d;
// Derived* => Base* 의 암시적 변환이 이루어진다. 런타임에!
Base *pb = &d;

위에서, 객체 d 가 가질 수 있는 주소는 2개 이상이 될 수 있다.

캐스팅과 관련된, '보기엔 맞는 것 같지만 실제로는 틀린' 코드를 보자.

class Window
{
  public:
    // 기본 클래스의 onResize 구현
    virtual void onResize() { ... }
  ...
};

class SpecialWindow: public Window
{
  public:
    virtual void onResize()
    {
      // 동작이 안 됨
      static_cast<Window>(*this).onResize();
      
      // SpecialWindow 에서만 필요한 작업을 여기서 수행
      ...
    }
  ...
};

SpecialWindow 클래스에서 onResize 함수 내의 캐스팅은 제대로 이루어지지 않는다. 왜냐하면 캐스팅이 일어나면서 *this 의 기본 클래스 부분에 대한 사본이 임시적으로 만들어지게 되어 있는데, 지금의 onResize 는 바로 이 임시 객체에서 호출된 것이다! 따라서 위 코드는 현재의 객체에 대해 Window::onResize 를 호출하지 않는다.
코드를 수정하려면 캐스팅을 빼고 다음과 같이 고치면 된다.

class SpecialWindow: public Window
{
  public:
    virtual void onResize()
    {
      // *this 에서 Window::onResize 를 호출
      Window::onResize();
      // SpecialWindow 에서만 필요한 작업을 여기서 수행
      ...
    }
  ...
};


마지막으로, dynamic_cast 를 보자.
dynamic_cast 는 비용이 매우 큰 캐스팅으로, 필요한 경우가 있지만 최대한 사용을 피하는 식으로 설계 및 구현을 하는 게 좋다. 다음 예시를 보자.

class Window { ... };

class SpecialWindow: public Window
{
  public:
    void blink();
    ...
};

typedef std::vector<std::shared_ptr<Window>> VPW;
VPW winPtrs;

...
for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
  // 고비용 : dynamic_cast 를 사용중
  if (SpecialWindow *psw = dynamic_cast<SpecialWindow*>(iter->get()))
    psw->blink();
}

dynamic_cast 사용을 피하기 위해, 코드를 다음과 같이 수정해볼 수 있다.

typedef std::vector<std::shared_ptr<SpecialWindow>> VPSW;
VPSW winPtrs;

...
for (VPSW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
  (*iter)->blink();
}

물론 이는 SpecialWindow 말고도 Window 의 다른 파생 클래스가 있을 경우 각기 다른 파생 클래스 타입의 벡터를 만들어 주어야 한다는 단점이 존재한다. 아니면 이런 방법도 있다.

class Window 
{ 
  public:
    // '아무 동작 안하는' 가상 함수
    // 별로 좋은 아이디어는 아니다 (항목 34 참조)
    virtual void blink() {}
    ...
};

class SpecialWindow: public Window
{
  public:
    // 실제 구현
    virtual void blink() { ... }
    ...
};

typedef std::vector<std::shared_ptr<Window>> VPW;
VPW winPtrs;

...

for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
  (*iter)->blink();
}


정말 피해야 하는 설계가 하나 있는데, 바로 '폭포식(cascading) dynamic_cast' 라고 불리는 구조이다. 예시를 보자.

class Window { ... };

typedef std::vector<std::shared_ptr<Window>> VPW;
VPW winPtrs;

...

for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
  if (SpecialWindow1 *psw1 = dynamic_cast<SpecialWindow1*>(iter->get())) { ... }
  else if (SpecialWindow2 *psw2 = dynamic_cast<SpecialWindow2*>(iter->get())) { ... }
  else if (SpecialWindow3 *psw3 = dynamic_cast<SpecialWindow3*>(iter->get())) { ... }
  ...
}

위의 설계는 심지어 새로운 파생 클래스가 생길 때 마다 하나를 더 추가해야 하는, 절대 피해야 하는 설계이다!

Comments