[C++] Chapter 16 - C++의 형 변환 연산자와 맺는 글

Lee Jeong Min·2021년 1월 24일
0

Cpp

목록 보기
16/16
post-thumbnail

16-1 C++에서의 형 변환 연산

C언어의 형 변환 연산자(오래된 C 스타일 형 변환 연산자)

PowerfullCasting.cpp

#include <iostream>
using namespace std;

class Car
{
private:
	int fuelGauge;
public:
	Car(int fuel) : fuelGauge(fuel)
	{}
	void ShowCarState() { cout << "잔여 연료량: " << fuelGauge << endl; }
};

class Truck : public Car
{
private:
	int freightWeight;
public:
	Truck(int fuel, int weight) : Car(fuel), 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();
	return 0;
}

기초 클래스의 포인터 형을 유도 클래스의 포인터 형으로 형 변환하는것은 일반적인 경우의 형 변환은 아니다. 또한 문제가 바로 보이는 형 반환과 같이 저러한 형 변환 연산은 문제가 되지만 위는 C 스타일의 형 변환 연산자인 '무적의 형 변환 연산자'여서 변환이 가능하게 해준다.
하지만 그 결과, 화물의 무게를 출력하는 부분에 있어서 의미없는 값이 들어가게 된다.

C++ 에서는 총 4개의 형변환 연산자를 제공한다.

  • static_cast
  • const_cast
  • dynamic_cast
  • reinterpret_cast

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

다이내믹 캐스트의 경우 상속관계에 놓여 있는 두 클래스 사이에서 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우 사용한다.
DynamicCasting.cpp

#include <iostream>
using namespace std;

class Car
{
private:
	int fuelGauge;
public:
	Car(int fuel) : fuelGauge(fuel)
	{}
	void ShowCarState() { cout << "잔여 연료량: " << fuelGauge << endl; }
};

class Truck : public Car
{
private:
	int freightWeight;
public:
	Truck(int fuel, int weight) : Car(fuel), freightWeight(weight)
	{}
	void ShowTruckState()
	{
		ShowCarState();
		cout << "화물의 무게: " << freightWeight << endl;
	}
};

int main(void)
{
	Car* pcar1 = new Truck(80, 200);
	Truck* ptruck1 = dynamic_cast<Truck*>(pcar1); // 컴파일 에러

	Car* pcar2 = new Car(120);
	Truck* ptruck2 = dynamic_cast<Truck*>(pcar2); // 컴파일 에러

	Truck* ptruck3 = new Truck(70, 150);
	Car* pcar3 = dynamic_cast<Car*>(ptruck3); // 컴파일 OK!
	return 0;
}

기초클래스 : Car, 유도클래스 : Truck인 상황에서 Truck의 포인터 및 참조형 데이터를 Car의 포인터 및 참조형 데이터로 형 반환해주는 모습을 보여준다.

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

static_cast는 dynamic_cast의 특성 뿐만아니라 기초클래스의 포인터 및 참조형 데이터도 유도 클래스의 포인터 및 참조형 데이터로 아무런 조건없이 형 변환시켜준다. 그러나 이에 대한 책임은 프로그래머가 져야 한다.

StaticCasting.cpp

#include <iostream>
using namespace std;

class Car
{
private:
	int fuelGauge;
public:
	Car(int fuel) : fuelGauge(fuel)
	{}
	void ShowCarState() { cout << "잔여 연료량: " << fuelGauge << endl; }
};

class Truck : public Car
{
private:
	int freightWeight;
public:
	Truck(int fuel, int weight) : Car(fuel), freightWeight(weight)
	{}
	void ShowTruckState()
	{
		ShowCarState();
		cout << "화물의 무게: " << freightWeight << endl;
	}
};

int main(void)
{
	Car* pcar1 = new Truck(80, 200);
	Truck* ptruck1 = static_cast<Truck*>(pcar1); // 컴파일 OK!
	ptruck1->ShowTruckState();
	cout << endl;

	Car* pcar2 = new Car(120);
	Truck* ptruck2 = static_cast<Truck*>(pcar2); // 컴파일 Ok 그러나 !
	ptruck2->ShowTruckState();
	return 0;
}

static_cast 연산자는 dynamic_cast 연산자와 달리, 보다 많은 형 변환을 허용한다. dynamic_cast 연산자를 사용할 수 있는 경우에는 dynamic_cast연산자를 사용해서 안전성을 높여야 하며, 그 이외의 경우에는 정말 책임질 수 있는 상황에서만 제한적으로 static_cast 연산자를 사용해야한다.

일반적으로 static_cast 연산자는 C언어의 형변환 연산자보다 더 적은 범위의 변환을 허용하기 때문에 static_cast를 주로 이용하는 것이 안전함 (C언어의 일반적인 형 변환 연산자보다)

const_cast: const의 성향을 삭제하라!

ConstCasting.cpp

#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로 선언된 char(별) 변수를 매개변수가 char(별) 로 받는 함수에 넣기위해 const_cast를 사용하였다. const int또한 마찬가지 const_cast를 사용하여 인자전달.

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

포인터를 대상으로 하는, 그리고 포인터와 관련이 있는 모든 유형의 형 변환을 허용한다.
ReinterpretCasting.cpp

#include <iostream>
using namespace std;

int main(void)
{
	int num = 0x010203;
	char* ptr = reinterpret_cast<char*>(&num);

	for (int i = 0; i < sizeof(num); i++)
		cout << static_cast<int>(*(ptr + i)) << endl;
	return 0;
}

int형 정수에 바이트 단위 접근을 위해서 int형 포인터를 char형 포인터로 형 반환하고 있다.

dynamic_cast 두 번째 이야기: Polymorphic 클래스 기반의 형 변환

dynamic_cast또한 static_cast처럼 기초클래스의 포인터 및 참조형 데이터를 유도클래스의 포인터 및 참조형 데이터로 형 변환이 가능하다. 단, 기초클래스가 Polymorphic 클래스인 경우에만!

Polymorphic 클래스랑 하나 이상의 가상함수를 지니는 클래스를 뜻한다.
PolymorphicDynamicCasting.cpp

#include <iostream>
using namespace std;

class SoSimple // Polymorphic 클래스! ShowSimpleInfo가 가상함수 이므로
{
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* sim = new SoComplex;
	SoComplex* comPtr = dynamic_cast<SoComplex*>(sim);
	comPtr->ShowSimpleInfo();
	return 0;
}

포인터 변수 sim이 실제로 가리키는 객체가 SoComplex이기 때문에 가상함수를 사용하였을 시 dynamic_cast로 변환을 하여도 문제가 되지 않는다! 하지만 위에서 new SoComplex가 아닌 new SoSimple이었다면 형변환을 하지 못하고 NULL 값을 반환한다.
dynamic_cast는 안정적인 형 변환을 보장하며, 컴파일 시간이 아닌 실행시간에 안전성을 검사하도록 컴파일러가 바이너리 코드를 생성한다.

dynamic_cast 연산자를 이용한 형 변환의 과정에서 발생할 수 있는 예외는 bad_cast이며 what()함수를 이용해 예외의 원인을 표현할 수 있다. 이러한 예외가 발생할 수 있기 때문에 반드시 이에대한 예외처리를 해야한다고 한다.


이렇게 '윤성우의 열혈 C++ 프로그래밍' 책이 끝났다. C++을 공부하면서 C언어를 하면서 몰랐던 부분이나 이해가 되지 않았던 부분도 많이 공부하면서 알게되어 좋았고 맺는말을 읽으면서 앞으로 어떠한 것을 공부하면 좋을 지, 공부할 때 어떤 마음가짐을 가지는게 좋은 지 알게 되었다. 앞으로 C++을 많이 사용하게 될지는 모르겠지만 주로 PS를 할때 C++을 사용하면서 많이 익숙해지게 노력해야겠다.

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글