본문 바로가기
Programming/C++

C++의 형 변환 연산자

by OKOK 2017. 8. 2.

C++ 진영에서는 C 스타일의 형 변환 연산자를 가리켜 오래된 C 스타일 형 변환 연산자라 부르기도 합니다. 이렇듯 C 스타일의 형 변환 연산자는 C언어와의 호환성을 위해서 존재할 뿐, C++에서는 새로운 형 변환 연산자와 규칙을 제공하고 있스니다. 


#include <iostream>

using namespace std;


class Car

{

private:

int fuelGauge;

public:

Car(int fuel) : fuelGauge(fuel)

{ }

void ShowCarState() { cout << "fuel: " << fuelGauge << endl; }

};


class Truck : public Car

{

private:

int freightWeight;


public:

Truck(int fuel, int weight)

: Car(fuel), freightWeight(weight)

{ }

void ShowTruckState()

{

ShowCarState();

cout << "weight: " << 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();

return 0;


포인터 변수가 가리키는 대상이 실제로는 Truck 객체이기 떄문에 33행의 형변환 연산은 문제가 되지 않을 수 있습니다. 하지만, 기초 클래스의 포인터 형을 유도 클래스의 포인터 형으로 형 변환하는 것은 일반적인 경우의 형 변환이 아닙니다. 포인터 변수가 가리키는 대상이 실제로는 Car 객체이기 때문에 형 변환 연산은 문제가 된다. 하지만 C 스타일의 형 변환 연산자는 컴파일러로 하여금 이러한 일이 가능하게 합니다. 무적의 형 변환 연산자이기 때문입니다. 이 문장의 실행결과는 예측이 불가능 합니다. 포인터 변수가 가리키는 대상은 객체이기 때문에 함수의 호출은 논리적으로 맞지가 않으며, 특히 이 객체에는 화물의 무게를 의미하는 메버가 존재하지 않습니다. 


컴파일러는 다음과 같이 실행합니다. 이것이 맞는 형 변환 연산인지 아닌지는 모르겠지만, 형 변환을 하라니 하겠습니다. 즉, 컴파일러는 최대한 프로그래머의 실수를 지적해주고 싶은 마음이지만, C 스타일의 형 변환 연산을 했기 때문에 잘못된 형 변환 연산인지 아닌지를 지적해 줄 수 없는 상황입니다. 포인터 변수 pcar1이 가리키는 것이 Truck 객체니까 이는 의도적으로 작성된 코드입니다. Truck형 포인터가 Truck 객체를 가리키는 정상적인 상황이 되었잖습니까. 물론 결고만 놓고 보면 컴파일도 실행결과에도 문제는 없습니다. 하지만 이는 불필요한 형 변환입니다. 이렇게 형 변환해야만 하는 상황이라면, Truck 객체를 Car형 포인터 변수로 가리키게 했을 리 없잖습니까. 기초 클래스의 포인터 형을 유도 클래스의 포인터 형으로 변환하는 것은 일반적인 연산이 아닙니다. 


dynamic_cast 상속관계에서의 안전한 형 변환

dynamic_cast를 시작으로  C++ 의 형 변환 연산자를 하나씩 설명하겠습니다. dynamic_cast 형 변환 연산자는 다음의 형태를 갖습니다. dynamic_cast<T>(expr) 상속관계에 놓여 있는 두 클래스 사이에서 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우입니다. 


상속관계에 있는 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로 형 변환하겠습니다. 


static_cast: A차입에서 B타입으로

유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로뿐만 아니라, 기초 클래스의 포인터 및 참조형 데이터도 유도 클래스의 포인터 및 참조형 데이터로 아무런 조건 없이 형 변환시켜 주세요.


#include <iostream>

using namespace std;


class Car

{

private:

int fuelGauge;

public:

Car(int fuel) : fuelGauge(fuel)

{ }

void ShowCarState() { cout << "fuel: " << fuelGauge << endl; }

};


class Truck : public Car

{

private:

int freightWeight;


public:

Truck(int fuel, int weight)

: Car(fuel), freightWeight(weight)

{ }

void ShowTruckState()

{

ShowCarState();

cout << "weight: " << freightWeight << endl;

}

};


int main(void)

{

Car * pcar1 = new Truck(80, 200);

Truck * ptruck1 = static_cast<Truck*>(pcar1);

ptruck1->ShowTruckState();

cout << endl;


Car * pcar2 = new Car(120);

Truck * ptruck2 = static_cast<Truck*>(pcar2);

ptruck2->ShowTruckState();

return 0;


const_cast: const 의 성향을 삭제하시오.


#include <iostream>


using namespace std;


void ShowString(char* str)

{

cout << str << endl;

}


void ShowAddResult(int& n1, int& n2)

{

cout << n1 + n2 << endl;

}


int main(void)

{

const char * name = "Lee Sung Ju";

ShowString(const_cast<char*>(name));


const int& num1 = 100;

const int& num2 = 200;

ShowAddResult(const_cast<int&>(num1), const_cast<int&>(num2));

return 0;


문자열의 주소 값을 인자로 전달받는 함수가 정의되었습니다. 인자로 전달되는 변수를 참조형으로 전달받는 함수가 정의되었습니다. 이렇듯 const_cast 형 변환 연산은, 함수의 인자전달 시 const 선언으로 인한 형의 불일치가 발생해서 인자의 전달이 불가능한 경우에 유용하게 사용이 됩니다. 그래도 이렇게 const_cast 연산자가 존재함으로 인해서, 키워드 const 선언에 값의 변경을 허용하지 않는다 라는 의미를 부여하기 어려워진 것 같습니다. 


reinterpret_cast:상관없는 자료형으로의 형 변환

기본적인 형태는 다음과 같습니다. 포인터를 대상으로 하는, 그리고 포인터와 관련이 있는 모든 유형의 형 변환을 허용합니다. 


dynamic_cast : Ploymorphic 클래스 기반의 형 변환

상속관계에 놓여있는 두 클래스 사이에서, 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데ㅣ터로 형 변환할 경우에는 dynamic_cast 연산자를 사용합니다. 상속관계에 놓여있는 두 클래스 사이에서, 기초 클래스의 포인터 및 참조형 데이터를 유도 클래스의 포인터 및 참조형 데이터로 형 변환할 경우에는 static_cast 연산자를 사용합니다. 


#include <iostream>

using namespace std;


class SoSimple

{

public:

virtual void ShowSimpleInfo()

{

cout << "SoSimple Base Class" << endl;

}

};


class SoComplex : public SoSimple

{

public:

void ShowSimpleInfo()

{

cout << "SoComplex derived Class" << endl;

}

};


int main(void)

{

SoSimple * simPtr = new SoComplex;

SoComplex * comPtr = dynamic_cast<SoComplex*>(simPtr);

comPtr->ShowSimpleInfo();

return 0;


SoSimple 클래스가 Polymorphic 클래스이므로 SoComplex 클래스도 Polymorphi 클래스 입니다. 포인터 변수 simPtr이 실제 가리키는 것은 Socomplex 객체입니다. 기초 클래스인 형 포인터 변수를 유도 클래스인 형 포인터로 형 변환하고 있습니다. 기초 클래스인 클래스 이므로 연산자로 형 변환이 가능합니다. 


#include <iostream>

using namespace std;


class SoSimple

{

public:

virtual void ShowSimpleInfo()

{

cout << "SoSimple Base Class" << endl;

}

};


class SoComplex : public SoSimple

{

public:

void ShowSimpleInfo()

{

cout << "SoComplex derived Class" << endl;

}

};


int main(void)

{

SoSimple * simPtr = new SoSimple;

SoComplex * comPtr = dynamic_cast<SoComplex*>(simPtr);

if (comPtr == NULL)

cout << "change" << endl;

else

comPtr->ShowSimpleInfo();

return 0;


안정적이지 못한 형 변환을 시도하고 있습니다. 이러한 경우 dynamic_cast 연산자는 NULL을 반환합니다. NULL의 반환 유무를 판단해서 형 변환에 성공했을 때에만 멤버함수를 호출하도록 작성되었습니다. 이렇듯 dynamic_cast 는 안정적인 형 변환을 보장합니다. 컴파일 시간이 아닌 실행 시간에 안정성을 검사하도록 컴파일러가 바이너르 코드를 생성합니다. 


#include <iostream>

using namespace std;


class SoSimple

{

public:

virtual void ShowSimpleInfo()

{

cout << "SoSimple Base Class" << endl;

}

};


class SoComplex : public SoSimple

{

public:

void ShowSimpleInfo()

{

cout << "SoComplex Derived Class" << endl;

}

};


int main(void)

{

SoSimple simObj;

SoSimple& ref = simObj;


try

{

SoComplex& comRef = dynamic_cast<SoComplex&>(ref);

comRef.ShowSimpleInfo();

}


catch (bad_cast expt)

{

cout << expt.what() << endl;

}


return 0;


참조자 ref가 실제 참조하는 대상이 SoSimple 객체이기 때문에 SoComplex 참조형으로의 형 변환은 안전하지 못합니다. 참조자를 대상으로 NULL을 반환할 수 없기 때문에 이러한 상황에는 bad_cast 예외가 발생합니다. 예외의 바생으로 인해서 실행되지 못합니다. 예외처리 부분에서도 언급한적 있지만, what 함수는 예외의 원인을 문자열의 형태로 반환합니다. 컴파일러마다 반환하는 문자열의 내용에는 차이가 있습니다.