[C++] OOP (4) Type Casting
부모 타입의 포인터가 자식 타입의 객체를 가리키도록 해서 UpCasting을 구현할 수 있는데..
자식 객체는 부모 객체보다 더 많은 정보를 가지고 있기 때문에 부모 타입의 포인터로 자식 타입만 가지고 있는 정보에 접근하는건 불가능하다.
부모 타입인 Car에는 money 메서드가 없지만, 자식 타입인 Bus에는 money 메서드가 있다.
따라서 Car* 타입으로는 자식 타입인 Bus의 정보에 접근 할 수 없다.
foo 내부 함수를 바꿔보자.
void foo(Car* car) {
Bus* bus = static_cast(car);
bus->money();
}
Car* 타입을 Bus* 타입으로 바꿨다. (DownCasting)
DownCasting은 위험할 수 있다.
위에서 언급했듯 부모보다 자식에 정보가 더 많이 들어있기 때문에, 변수를 잘못 호출하면 프로그램이 죽을 수 있다.
예시를 살펴보자.
class A{
public:
int a_;
};
class B : public A{
public:
int b_;
};
int main(){
A* a = new A;
B*b = (B*)a;
b->b_;
}
b에는 b_ 값이 없어 프로그램이 비정상적으로 작동할 수 있다.
DownCasting의 안전성은 런타임 시의 타입에 달려있다.
이렇듯 DownCasting은 위험하지만.. DownCasting이 주는 이점을 포기할 수 없어 대부분의 언어들은 DownCasting을 지원한다.
C나 Java에서는 타입 변환 시 () 기호를 사용해서 처리했다.
(Type) Expression
ex.) (int)a (Car)bus
이 방법은 두 가지 문제점이 있다.
1. Unnoticeability
괄호 () 는 연산 순서를 정할 때도 사용되고, 메서드를 호출할 때도 사용된다.
2. Unsafeness
DownCasting 시 안전을 보장하지 못한다. (DownCasting 외에도 ()로 형변환하는 작업은 안전하지 않다)
C++에서는 ()를 사용한 타입 변환도 지원하지만, 네 가지 타입 캐스팅 방법 또한 제공한다.
1. static_cast
static_cast<Type>(Expression)
컴파일 시점에 Class Hierarchy을 기반으로 Type Casting 여부를 확인한다.
런타임 오버헤드가 없다는 장점이 있지만, DownCasting 시 안전을 보장하지 못한다.
Class Hierarchy는 단 방향 그래프로 표현되고, static_cast 시 역방향 / 정방향 중 하나만 선택해서 두 클래스가 연결된다면 Type Casting이 가능하다고 판단한다.
Car* 타입인 bus_car를 Bus* 타입으로 변환한다. Car과 Bus는 연결되니 변환할 수 있다.
Car* 타입인 bus_car를 ConvertibleCar* 타입으로 변환한다. Car과 ConvertibleCar는 연결되니 변환할 수 있다.
Bus* 타입을 ConvertibleCar* 타입으로 변환한다. Bus와 ConvertibleCar은 한 가지 방향으로 연결할 수 없으니 변환이 불가능하다.
변환할 수 있으면 변환된 값을 반환하고, 변환할 수 없으면 컴파일 에러를 발생시킨다.
컴파일 에러를 발생시키지 않았다고 해서 사용할 때 런타임 에러가 발생하지 않는건 아니다.
2. dynamic_cast
dynamic_cast<Type>(Expression)
Expression타입이 어떤 객체를 가리키고 있는지 확인하고, 그 객체가 Type이거나 Type의 자손 타입이면 변환할 수 있다고 판단한다.
변환할 수 있으면 변환된 값을 반환하고, 변환할 수 없으면 nullptr을 반환한다.
static_cast와 달리 컴파일에러를 발생시키지 않아 if 문법과 함께 사용할 수 있다.
런타임 시 포인터를 따라가면 어떤 객체를 가리키고 있는지 확인하려면 컴파일러가 프로그램을 바꿔서 각각을 tagging하도록 설정해야 한다.
즉, RTTI(RunTime Type Information)를 설정해 줘야 한다. (런타임 시점에서의 타입)
clang / gcc는 기본값으로 RTTI를 켜기 때문에 따로 설정할 필요가 없다. (윈도우의 C++컴파일러는 별도로 설정이 필요하다)
이 부분 때문에 오버헤드를 잡아먹고, 성능 저하가 발생한다.
dynamic_cast를 사용하기 위해서는 각각의 클래스들이 polymorphic type이여야 한다.
polymorphic type은 해당 클래스가 virtual 키워드를 가진 destructor를 가지고 있어야 함을 의미하는데... 관련 내용은 다음에 알아보자.
해당 포인터가 실제로 가리키고 있는 타입이 판단의 대상이다.
<Bus*>(bus_car) : bus_car는 Bus를 가리키고 있다. Bus와 Bus가 일치하니 변환할 수 있다.
<Car*>(bus_car) : bus_car는 Bus를 가리키고 있다. Bus는 Car의 자손 타입이니 변환할 수 있다.
<ConvertibleCar*>(bus_car) : bus_car는 Bus를 가리키고 있다. Bus와 ConvertibleCar는 조건을 충족하지 않는다. 변환할 수 없다.
<Z4ConvertibleCar*>(conv_car) : conv_car는 ConvertibleCar를 가리키고 있다. Z4ConvertibleCar는 ConvertibleCar의 자손이기 때문에 변환할 수 없다.
dynamic_cast를 사용하면 타입의 안전을 보장할 수 있다.
<Type>(Expression) 에서 Expression이 Type의 자식 타입이거나 같은 타입 일 경우에만 캐스팅을 시도하기 때문...
판단의 대상은 포인터가 실제로 가리키고 있는 타입이고, 캐스팅 대상은 포인터임에 주의하자!!
3. reinterpret_cast
reinterpret_cast<Type>(Expression)
컴파일 에러를 발생시키지 않고, 런타임 오버헤드도 없다. (상황에 따라 런타임 에러는 발생할 수 있다)
말 그대로 무조건 다시 해석한다. (Expression을 Type으로)
포인터를 통해 메모리를 관리할 때 사용된다.
4. const_cast
const_cast<Type>(Expression)
const 키워드가 붙은 변수에 const를 지워주는 역할을 한다.
되도록이면 사용하지 말자!
'Programming Language > C++' 카테고리의 다른 글
[C++] 정리 (1) (0) | 2022.11.23 |
---|---|
[C++] OOP (5) Dynamic dispatch / Multiple Inheritance (0) | 2022.11.22 |
[C++] OOP (3) Inheritance / 연산자 오버로딩 (1) | 2022.10.12 |
[C++] C++과 Java (1) | 2022.10.05 |
[C++] Memory Allocation / static (0) | 2022.10.05 |
댓글
이 글 공유하기
다른 글
-
[C++] 정리 (1)
[C++] 정리 (1)
2022.11.23 -
[C++] OOP (5) Dynamic dispatch / Multiple Inheritance
[C++] OOP (5) Dynamic dispatch / Multiple Inheritance
2022.11.22 -
[C++] OOP (3) Inheritance / 연산자 오버로딩
[C++] OOP (3) Inheritance / 연산자 오버로딩
2022.10.12 -
[C++] C++과 Java
[C++] C++과 Java
2022.10.05