C++ 진영에서 C 스타일의 형 변환 연산자를 가리켜 '오래된 C 스타일 형 변환 연산자(Old C-style cast operator)'라 부르기도 한다. 이렇듯 C 스타일의 형 변환 연산자는 C언어와의 호환성을 위해서 존재할 뿐, C++에서는 새로운 형 변환 연산자와 규칙을 제공하고 있다.
C언어의 형 변환 연산자는 너무나도 강력해서 변환하지 못하는 대상이 없다.
따라서 아래의 예제에서 보이는 실수를 해도 컴파일러는 이를 잡아내지 못한다.
class Car{
private:
int fuelGauge;
public:
Car(int fuel): fuelGauge(fuel)
{}
void showCarState(){ cout << feulGauge << endl;}
};
class Truck : public Car
{
private:
int freightWeight;
public:
Truck(int fuel, int weight) : Car(feul), freightWeight(weight)
{}
void ShowTruckState()
{
ShowCarState();
cout << "화물의 무게: " <<freightWeight << endl;
}
};
int main(void)
{
Car * pcar1= new Truck(80, 200);
Truck * ptruck1=(Truck *)pcar1;// 문제 없어 보이는 형변환!. //사실 의도가 확실한지 실수인지 모름
ptruck1->ShowTruckState();
cout << endl;
Car * pcar2= new Car(120);
Truck * ptruck2=(Truck *)pcar2; // 문제가 바로 보이는 형변환!
ptruck2->ShowTruckState();
cout << endl;
}
위 예시에서 Truck * ptruck1=(Truck *)pcar1
이 형 변환 연산은 문제가 되지 않을 수 있다. 하지만, 기초 클래스의 포인터 형을 유도 클래스의 포인터 형으로 형 변환 하는것은 일반적인 경우의 형 변환이 아니다.
더욱 큰일인 문장은
Truck * ptruck2=(Truck *)pcar2
... 이게 가능..한가...?
포인터 변수 pcar2가 가리키는 대상이 실제로는 Car객체이기 때문에 유도 클래스로의 형 변환 연산은 "당연히" 문제가 된다.
무적의 형 변환 연산자
이기 때문이다...ptruck2->ShowTruckState()
이런 막돼먹은 호출은 논리적으로 맞지도 않고 이 포인터가 가지고 있는 객체에는 화물의 무게를 의미하는 freightWeight
가 없기 때문에 우리의 상식대로라면 에러가 나야된다.하지만 우리의 무적의 C 형변환 연산자는 그냥 설정도 안되어있는 freightWright에 접근하여 쓰레기값을 반환한다... 경악..
위의 형 변환 연산자들을 사용하면 프로그래머는 자신이 의도한 바를 명확히 표시 할 수 있어, 컴파일러도 프로그래머의 실수를 지적해 줄 수 있고, 다른 프로그래머들도 코드를 직접 작성한 프로그래머의 실수여부를 판단할 수 있다.
dynamic_cast 형 변환 연산자는 다음의 형태를 갖는다.
dynamic_cast<T>(expr)
<>
사이에 변환하고자 하는 자료형의 이름을 두되, 객체의 포인터는 참조형이 와야 하며,() 사이에는 변환의 대상이 와야 한다.상속관계에 놓여 있는 두 클래스 사이에서 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 초인터 및 참조형 데이터로 형 변환하는 경우
int main(void)
Car * pcar1 = new Truck(200, 3000);
//아무리 원래 객체가 Truck 포인터 객체라 하더라도 Car 포인터로 받아진 이상 다시 유도 클래스로 들어가는 것을 막는다.
Truck * ptruck1 = dynamic_cast<Truck*>(pcar1); //컴파일 에러! (기초 클래스 객체 포인터가 유도클래스에 객체포인터에 들어가는 것을 방지!
Car * pcar2 = new Car(200, 3000);
//위에것도 안됬는데 이게 될일이 없다. 컴파일 에러 발생
Truck * ptruck2 = dynamic_cast<Truck*>(pcar2);
// 컴파일 성공! 옳게 된 형 변환이다.
Truck * ptruck3 = new Truck(200, 3000);
Car * pCar3 = dynamic_cast<Truck*>(ptruck3);
정리하면 dynamic_cast 연산자를 사용했다는 것은 다음과 같은 뜻이다.
상속관계에 있는 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로 형 변환 하겠다!
staic_cast 형 변환 연산자는 다음의 형태를 갖는다.
Car * pcar1 = new Truck(200, 3000);
Truck * ptruck1 = dynamic_cast<Truck*>(pcar1); // 이거 된다. 사실 원래 객체가 truck이라 프로그래머가 알고 쓸경우 큰 문제없다.
Car * pcar2 = new Car(200, 3000);
Truck * ptruck2 = static_cast<Truck*>(pcar2); // 이게 된다... 프로그래머가 잘못쓴것... 컴파일러는 이걸 변환해준다...
static_cast<T>(expr)
static_cast 연산자는 dynamic_cast 연산자와 달리, 보다 많은 형 변환을 허용한다.
하지만 그에 따른 책임도 프로그래머가 져야 하기 때문에 신중하게 선택해서 사용해야한다.
가능한한 dynamic_cast 연산자를 사용할 수 있으면 dynamic_cast를 사용하여 안정성을 높이고,
정말 책임질 수 있는 상황에서만 제한적으로 static_cast를 사용하자.
근데 dynamic보다 static이 연산의 속도가 좀더 빠르다... 이러한 이유로 속도가 중요할땐 static을 그냥 쓰는 경우도 있다.
기본 자료형 데이터간의 형 변환에도 사용된다. 이때, C언어 형 변환연산자랑 다른점은 Static_cast는
상속관계에 있는 클래스의 포인터 및 참조형 데이터의 형 변환 아니면 기본 자료형 데이터의 형 변환
만을 지원하기 때문, C언어의 형 변환연산자 같이 무시무시한 변환은 안한다.
ex)
const int num =20;
int * ptr = (int*)#
*ptr = 30; //-> 이게 된다... const인데 값 변경이 가능해진다... 노오오올라울 따름..
위와 같이 const
성향을 무시하고 싶을 때가 있을때 사용한다.(근데 그럴때가...)
C++에서는 포인터와 참조자의 const 성향을 제거하는 형 변환을 목적으로, 다음의 형 변환 연산자를 제공하고 있다.
const_cast<T>(expr)
void ShowString(char* str){
cout << str <<endl;
}
void ShowAddResult(int& n1, int& n2){
cout << n1+n2 << endl;
}
int main(void){
const char* name = "RisingJade";
ShowString(const_cast<char*>(name));//원래대로라면 const인 name은 char* str에 들어갈 수 없지만 캐스팅을 통해 const 속성을 없애서 가능해졌다
const int& num1 = 100;
const int& num2 = 200;
ShowAddresult(const_cast<int&>(num1),const_cast<int&>(num2)); // 이것도 const 속성이 없어져서 가능해진것
}
이렇듯 const_cast
형 변환 연산은, 함수의 인자전달 시 const
선언으로 인한 형의 불일치가 발생해서 인자의 전달이 불가능한 경우에 유용하게 사용이 된다.
const
선언의 의미가 반감되는 효과가 있으므로 긍정적인 측면이 잘 드러나는 경우에만 제한적으로 쓰자voletile
성향을 제거하는데도 사용할 수 있다.reinterpret_cast
연산자는 전혀 상관이 없는 자료형으로의 형 변환에 사용이 되며, 기본적인 형태는 다음과 같다.
reinterpret_cast<T>(expr)
reinterpret_cast 연산자는 포인터를 대상으로 하는, 그리고 포인터와 관련이 있는 모든 유형의 형 변환을 허용한다.
int num = 82;
int *ptr = #
int adr = reinterpret_cast<int>(ptr); // 주소 값을 정수로 변환
cout << "Address: " << adr << endl;//주소 값 출력
int rptr = reinterpret_cast<int*>(adr); // 정수를 다시 주소 값으로 변환
cout << " value : " << *rptr<<endl; // 주소 값에 저장된 정수 출력
reinterpret_cast
를 통해 위와 같은 일을 할 수 있다.
기초클래스가 Polymorphic 클래스인 경우
dynamic_cast
를 이용하여 기토 클래스의 포인터 및 참조형 데이터를 유도 클래스의 포인터 및 참조형 데이터로 변환 가능하다!
즉, 기초클래스가 Polymorphic 클래스인 경우 굳이 static_cast
를 안써도 된다는 의미이다.
dynamic_cast
를 쓸 수 있다.NULL
을 반환한다.class SoSimple{
public:
virtual void ShowSimpleInfo(){
cout << "SoSimple" << endl;
}
};
class SoComplex : publiv SoSimple{
public:
void ShowSimpleInfo(){
cout << "SoComplex" << endl;
}
};
int main(void){
SoSimple * simPtr = new SoComplex;
SoComplex * comPtr = dynamic_cast<SoComplex*>(simPtr);//원래대로면 안되야 하지만 SoSimple(기초 클래스)가 Polymorphic이라 가능!
comPtr->ShowSimpleInfo();
return 0;
}
_____
console:
SoComplex
여기서
SoSimple * simPtr = new SoComplex;
SoComplex * comPtr = dynamic_cast<SoComplex*>(simPtr);
이 부분이 가능했던것은 simPtr
이 가리키는 객체가 SoComplex 객체이기 때문이다. 즉, 포인터 변수 simPtr
이 가리키는 객체를 SoComplex
형 포인터 변수 comPtr
이 함께 가리켜도 문제되지 않기 때문에 성공한 것이다.
만약 이부분을
SoSimple * simPtr = new SoSimple; // 변경된 부분
SoComplex * comPtr = dynamic_cast<SoComplex*>(simPtr);
이와 같이 바꾸면 형 변환의 결과로 NULL
포인터가 반환된다.
프로그래머가 정의하지 않아도 발생하는 예외도 있다. 그런 유형의 예외 중 하나로 형 변환시 발생하는 bad_cast가 있다.
class SoSimple{
public:
virtual void ShowSimpleInfo(){
cout << "SoSimple" << endl;
}
};
class SoComplex : publiv SoSimple{
public:
void ShowSimpleInfo(){
cout << "SoComplex" << endl;
}
};
int main(void){
SoSimple simObj;
SoSimple& ref = simObj;
try{
//ref가 실제 참조하는 것은 SoSimple이다!! 이럴경우엔 polymorphic클래스라도 안된다.
SoComplex& comRef=dynamic_cast<SoComplex&>(ref);// 예외 발생!
comRef.ShowSimpleInfo(); // 예외 발생으로 인해 실행 안됨
}
catch(bad_cast expt){
cout << expt.what() << endl;
}
return 0;
}
참조자 ref
가 실제 참조하는 대상이 SoSimple
객체이기 때문에 SoComplex
참조형으로의 형 변환은 안전하지 못하다. 그리고 참조자를 대상으로는 NULL
을 반환할 수 없기 때문에 이러한 상황에서는 bad_cast
예외가 발생한다.
위에서 보이듯이, 참조형을 대상으로 dynamic_cast
연산을 진행할 경우에는 bad_cast
예외가 발생할 수 있기 때문에 반드시 이에 대한 예외처리를 해야한다.