일지

Effective C++...25

niamdank 2022. 7. 15. 09:03

캐스팅은 절약, 또 절약! 잊지 말자

C++의 캐스팅 방법

C++ 에는 C 버전의 구형 스타일 캐스트와 C++ 버전의 신형 스타일 캐스트가 존재한다.

  • 구형 스타일 캐스트 C 스타일
    • (T) 표현식
    • T(표현식)
  • 신형 스타일 캐스트 C++ 스타일
    • const_cast<T>(표현식) 객체의 상수성을 없애는 용도
    • dynamic_cast<T>(표현식) 안전한 다운 캐스팅에 사용, 런타임 비용이 굉장히 높다.
    • reinterpret_cast<T>(표현식) 하위 수준 캐스팅에 사용, 일반적으로 사용되지 않는다.
    • static_cast<T>(표현식) 암시적 변환을 강제로 진행할 때 사용

 

신형 스타일의 캐스트는 다음과 같은 장점이 있다.

  1. 코드를 읽을 때 알아보기 쉽기 때문에 타입 시스템이 망가졌을 때 디버깅이 쉽다.
  2. 목적을 좁혀서 캐스팅하기 때문에 컴파일러에서 에러를 체크할 수 있다.

 

C++에서 캐스트를 조심해야 하는 이유

C++를 쓸 때는 객체가 가질 수 있는 주소가 하나가 아닐 수도 있다.

 

다음 코드는 Derived*가 가리키는 주소와 Base*일 때 가리키는 주소가 다를 수 있음을 보여준다.


class Base { ... };

class Derived: public Base { ... };

 

Derived d;

Base *pb = &d; // Derived* 에서 Base*의 암시적 변환이 이루어진다.


 

※ C++를 쓸 때는 데이터가 어떤 식으로 메모리에 박혀 있을 거라는 섣부른 가정을 피해야 한다.

 

dynamic_cast가 느린 이유

특정 컴파일러에서는 클래스 이름에 대한 문자열 비교 연산에 기반을 두어 만들어졌다. 따라서 깊이가 깊어질수록 더 느려지게 된다.

 

정말 피해야 하는 설계로 폭포식 dynamic_cast라는 구조가 있는데 다음과 같은 구조를 말한다.


class Window { . . .};

... // 파생 클래스 정의

typedef std::vector<std::tr1::shared_ptr<window>> VPW;

VPW winPtrs;

...

for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)

{

    // Window 클래스가 바뀌거나 파생 클래스가 생겼을 때마다 관리를 해 줘야 하고 느리기까지 하다.

    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())) { ... }

}


 

dynamic_cast를 피하는 방법

두 가지 방법으로 dynamic_cast를 줄일 수 있다.

  • 파생 클래스의 포인터를 컨테이너에 담아 사용하는 방법
  • 가상 함수를 만들어 필요한 구현이 자연스럽게 호출되도록 하는 방법

 

※ dynamic_cast를 쓰기 적절해 보인다면 뭔가 잘못되가고 있는 것이다. 코드를 다시 확인하자.