(T) 표현식
T (표현식)
const_cast<T>(표현식)
dynamic_cast<T>(표현식)
reinterpret_cast<T>(표현식)
int
)static_cast<T>(표현식)
int
-> double
)void*
-> 일반 포인터, 기본 클래스 -> 파생 클래스)const_cast
로만 가능되도록이면 C++ 스타일의 캐스트를 쓰자!
- 코드를 알아보기 쉬워, 어디서 C++ 타입 시스템이 망가졌는지 찾기 편함
- 캐스트를 사용한 목적을 더 좁혀서 지정하기 때문에, 컴파일러에서 에러 진단 가능
- ex) 상수성을 없애려는 목적일 때 const_cast를 사용하지 않으면 컴파일되지 않음
class Widget {
explicit Widget(int size);
...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); // 함수 방식 캐스트
doSomeWork(static_cast<Widget>(15)); // C++ 방식 캐스트
이렇게 객체를 인자로 받는 함수에 객체를 넘기기 위해 명시호출 생성자를 호출할 때 뿐이다.
그러나 이런 경우에도 가능하면 신형 스타일의 캐스트를 사용하는 것이 좋다.
캐스팅으로 인해 타입변환이 있으면 런타임에 코드가 실행되는 경우가 있다.
int x, y;
...
double d = static_cast<double>(x)/y; // int -> double 명시적 변환
int
가 double
로 캐스팅될 때 int
와 double
의 표현구조가 다르므로 특정 코드가 실행된다.
class Base { ... };
class Derived: public Base { ... };
Derived d;
Base *pb = &d; // Derived* -> Base* 암시적 변환
두 포인터의 값이 같지 않다면(Derived 객체를 가리키는 주소가 한 개가 아니라면),
포인터의 offset을 Derived*
포인터에 적용하여 실제 Base*
포인터 값을 구하는 동작이 런타임에 이루어진다.
class Window
{
public:
virtual void onResize() { ... }
...
};
class SpecialWindow : public Window
{
public:
virtual void onResize()
{
static_cast<Window>(*this).onResize(); // *this의 사본을 만들어 Window의 OnResize를 호출
// Window::OnResize(); 이게 실제로 의도한 동작
... // SpecialWindow에서만 필요한 작업 수행
}
...
};
여기서 사용자가 실제로 의도한 동작을 하기 위해서는 static_cast<Window>(*this).onResize();
같이 캐스팅을 하지 말고,
Window::OnResize();
를 호출하기만 하면 된다.
예를 들어 깊이가 4인 단일 상속 계통에 속한 객체에 dynamic_cast
를 적용하면,
클래스 이름 비교를 위해 최대 4번 strcmp
가 호출된다.
그럼에도 이 연산자를 사용하고 싶을 때가 있다.
기본 클래스 포인터만 가지고 있는데, 파생 클래스의 함수를 호출하고 싶을 때이다.
이런 코드가 있다면..
class Window { ... };
class SpecialWindow : public Window
{
public:
void blink();
...
};
typedef
vector<shared_ptr<Window>> VPW;
VPW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
if (SpecialWindow *psw = dynamic_cast<SpecialWindow*>(iter->get()))
{
psw->blink();
}
}
class Window { ... };
class SpecialWindow : public Window
{
public:
void blink();
...
};
typedef
vector<shared_ptr<SpecialWindow>> VPSW;
VPSW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
(*iter)->blink();
}
애초에 파생 클래스인 SpecialWindow
의 벡터를 선언함으로써 기본 클래스인 Window
로 조작할 필요를 아예 없애는 것이다.
그러나 이 방법은 모든 Window
의 파생 클래스에 대한 포인터를 같은 컨테이너에 저장할 수 없다.
class Window
{
public:
virtual void blink() {} // 아무 동작 안함!
};
class SpecialWindow : public Window
{
public:
virtual void blink(); // SpecialWindow의 특정 동작 수행
...
};
typedef
vector<shared_ptr<Window>> VPW; // 모든 Window 파생 타입 객체를 담을 수 있음
VPW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begi(); iter != winPtrs.end(); ++iter)
{
(*iter)->blink(); // dynamic_cast 없이도 각 파생 클래스의 blick 함수 호출
}
이렇게 두 가지 방법으로 dynamic_cast
를 사용하는 것을 회피할 수 있다.
class Window { ... };
... // 파생 클래스 정의
typedef
vector<shared_ptr<Window>> VPW;
VPW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begi(); 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()))
{
...
}
...
}
이런 설계는 코드가 망가지기 매우 쉽다.
클래스가 추가되거나 빠질 때마다 위의 for문을 수정해야 하기 때문이다.
정리
- 캐스팅은 가능하면 피하자. 특히
dynamic_cast
는 더욱 신중히 생각해보자- 캐스팅이 필요하다면, 함수 안에 숨기자
- 구형 스타일의 캐스트보다 C++ 스타일의 캐스트를 사용하자.