Casting
C++는 const_cast() , static_cast() , dynamic_cast() , reinterpret_cast라는() 라는 네 가지 캐스팅 방법을 제공한다.
const_cast()
const_cast()는 변수에 const속성을 추가하거나 제거할 때 사용한다.
정석대로 처리하려면 프로그램 전체에서 const로 정의한 부분에 대하여 일관성을 유지해야 하지만 서드파티 라이브러리와 같이 마음대로 수정할 수 없는 경우 어쩔수 없이 const속성을 일시적으로 제거할 수 밖에 없다.
1
2
3
4
5
extern void ThirdPartyLibraryMethod(char* str);
void f(const char* str){
ThirdPartyLibraryMethod(const_cast< char*> (str));
}
static_cast()
명시적 타입 캐스팅
static_cast()는 언어에서 제공하는 명시적 변환 기능을 수행한다.
1
2
3
int i = 3;
int j = 4;
double res = static_cast<double>(i) / j;
사용자 정의 생성자에서의 캐스팅
사용자 정의 생성자나 변환 과정에서 허용하는 명시적 변환을 수행할 떄도 사용할 수 있다.
예를 들어 A 클래스의 생성자 중에 B 클래스 객체를 인수로 받는 경우, B 객체를 A 객체로 변환하는데 static_cast()를 이용할 수 있다. 다만 이러한 변환은 대부분 컴파일러가 알아서 처리해준다.
상속관계에서의 캐스팅
상속 계츨에서 하위 타입으로 다운캐스팅 할때도 static_cast()를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Base {
public:
virtual ~Base() = default;
};
class Derived : public Base {
public:
virtual ~Derived() = default;
};
int main() {
Base* b;
Derived* d = new Derived();
// 상속계층의 상위 타입으로 업캐스팅할 필요없다.
b=d;
// 상속계층의 하위 타입으로 다운캐스팅해야 한다.
d = static_cast<Derived*>(b);
Base base;
Derived derived;
Base& br = derived;
Derived& dr = static_cast<Derived&>(br);
return 0;
}
이러한 캐스팅은 포인터나 레퍼런스에도 적용할 수 있지만, 객체 자체에는 적용할 수 없다.
이렇게 static_cast() 을 사용할 때는 실행 시간에 타입 검사를 수행하지 않는다. 실행시간에 캐스팅 할 때는 Base와 Derived가 실제로 관련이 없어도 Base 포인터나 레퍼런스를 모두 Derived 포인터나 레퍼런스로 변환한다.
예를 들어 아래와 같은 경우, 컴파일 과정과 실행 과정에 아무 문제가 발생하지 않지만, 포인터 d를 사용하다가 객체의 범위를 벗어난 영역의 메모리를 덮어쓰는 심각한 문제가 발생할 수 있다.
1
2
Base* b = new Base();
Derived* d = static_cast<Derived*>(b);
불가능한 부분
포인터 타입이 서로 관련 없을 때는 static_cast()를 적용할 수 없다. 또한 변환 생성자가 제공되지 않는 타입의 객체도 static_cast()를 적용 할 수 없다. const 타입을 non-const 타입으로 변환 할 수 없고, int에 대한 포인터에도 적용할 수 없다. 기본적으로 C++의 타입규칙에 위배된는 것은 모두 할 수 없다.
dynamic_cast()
dynamic_cast()는 같은 상속 계층에 속한 타입끼리 캐스팅 할 떄 실행 시간에 타입을 검사한다. 포인터나 레퍼런스를 캐스팅할 떄 이를 이용할 수 있다.
dynamic_cast()는 내부 객체의 타입 정보를 실행 시간에 검사한다. 그래서 캐스팅하는 것이 적합하지 않다고 판단되면 포인터에 대해선는 널 포인터를 반환하고, 레퍼런스에 대해서는 std::bad_cast 익셉션을 반환한다.
예를 들어 다름과 같이 클래스 계층이 구성되어 있는경우를 살펴본다.
1
2
3
4
5
6
7
8
9
class Base {
public:
virtual ~Base() = default;
};
class Derived : public Base{
public:
virtual ~Derived() = default;
};
이떄 올바른 dynamic_cast()에 대한 사용은 다음과 같다.
- 부모클래스가 자식클래스의 생성자로부터 생성된 후, 자식클래스로 다운캐스팅
1
2
Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b);
- 자식클래스가 자식클래스의 생성자로부터 생성된 후, 부모클래스로 업캐스팅
1
2
Derived* d = new Derived();
Base* b = dynamic_cast<Base*>(d);
반면 올바르지 못한 dynamic_cast()에 대한 사용은 다음과 같다.
- 부모클래스가 부모클래스의 생성자로부터 생성된 후, 자식클래스로 다운캐스팅
1
2
3
4
5
6
Base* b = new Base();
Derived* d = dynamic_cast<Derived*>(b);
if(d == nullptr){
cout << "Bad cast!" << endl;
}
부모클래스가 자신의 생성자로부터 객체가 생성되므로, 자식클래스의 정보를 가지고 있지 않는다. 따라서 캐스팅할때 자식클래스의 생성자 정보를 모르므로 정상적인 캐스팅이 되지 않는다.
- 레퍼런스의 경우, 다음과 같이 적용하면 익셉션이 발생한다.
1
2
3
4
5
6
7
8
9
10
Base base;
Derived derived;
Base& br = base;
try {
Derived& dr = dynamic_cast<Derived&>(br);
}
catch (const bad_cast&) {
cout << "Bad cast!" << endl;
}
static_cast()나 reinterpret_cast()로도 같은 상속 계층의 하위 타입으로 캐스팅을 할 수 있다. 차이점으로는 dynamic_cast()는 실행 시간에 타입 검사를 수행한는 반면, static_cast()나 reinterpret_cast()의 경우는 문제가 되는 타입도 그냥 캐스팅 해버린다는 것이다.
실행시간의 타입정보는 객체의 vtable 에 저장된다. 따라서 dynamic_cast()를 적용하려면 클래스에 virtual 메소드가 적어도 한 개 이상 있어야 한다. 그렇지 않은 객체에 대해 dynamic_cast()를 적용하면 컴파일 에러가 발생한다.
reinterpret_cast()
reinterpret_cast()는 static_cast()보다 강력하지만 안전성은 좀 떨어진다. C++ 타입규칙에서 허용하지 않더라도 상황에 따라 캐스팅하는 것이 적합할 때 적용할 수 있다.
예를 들어 서로 관련이 없는 레퍼런스끼리 변환할 수도 있다. 마찬가지로 상속계층에서 아무런 관련이 없는 포인터 타입끼리도 변환할 수 있다. 이런 포인터는 흔히 void* 타입으로 캐스팅한다. 이 작 업은 내부적으로 처리되기 때문에 명시적으로 캐스팅하지 않아도 된다. 하지만 이렇게 void* 로 변환한 것을 다시 원래 타입으로 캐스팅할 때는 reinterpret_cast()를 사용해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class X {};
class Y {};
int main() {
X x;
Y y;
X* xp = &x;
Y* yp = &y;
// 서로 관련 없는 클래스 타입의 포인터를 변환할 때는 reinterpret_cast()를 써야 한다.
// static_cast()는 작동하지 않는다.
xp = reinterpret_cast<X*>(yp);
// 포인터를 void*로 변환할 때는 캐스팅하지 않아도 된다.
void* p = xp;
// 변환된 void*를 다시 원래 포인터로 복원할 때는 reinterpret_cast()를 써야 한다.
xp = reinterpret_cast<X*>(p);
// 서로 관련 없는 클래스 타입의 레퍼런스를 변환할 때는 reinterpret_cast()를 써야 한다.
// static_cast()는 작동하지 않는다.
X& xr=x;
Y& yr = reinterpret_cast<Y&>(x);
return 0;
}
reinterpret_cast ( )를 사용할 때는 주의해야 한다. 타입 검사를 하지 않고 변환할 수 있기 때문이다.
CAUTION : 포인터를 int 타입으로 변환하거나 그 반대로 변환할 때도 reinterpret_cast()를 사용할 수있다.단,이때int의크기가포인터를담을정도로충분히커야한다.예를들어64비트포인터를32비트 int로 변환하는 작업을 reinterpret_cast ( )로 처리하면 컴파일 에러가 발생한다.
캐스팅 정리
상황 | 캐스팅 방법 |
---|---|
const 속성 제거 | const_cast() |
안에서 허용되는 명시적 변환(int -> double, int -> bool) | static_cast() |
사용자 정의 생성자나 변환 연산자에서 지원하는 명시적 변환 | static_cast() |
서로 관련없는 타입의 객체끼리 변환 | 불가능 |
같은 상속 계층에 있는 클래스 타입의 객체 포인터 사이의 변환 | dynamic_cast() - 권장 static_cast() - 가능 |
같은 상속 계층에 있는 클래스 타입의 객체 레퍼런스 사이의 변환 | dynamic_cast() - 권장 static_cast() - 가능 |
서로 관련 없는 타입의 포인서 사이의 변환 | reinterpret_cast() |
서로 관련 없는 타압의 레퍼런스 사이의 변환 | reinterpret_cast() |
함수 포인터 사이의 변환 | reinterpret_cast() |
참고. Professional C++ 4/E - Marc Gregoire