C++/기타

[C++] type cast (static_cast, dynamic_cast, const_cast, reinterpret_cast)

sseram 2023. 5. 18. 13:06
반응형

프로그래밍 하다 보면 내가 원하든, 원하지 않든 자연스래 타입을 변경하여 사용할 때가 있다. int type을 bool로 변경하여 사용하거나, enum 값을 int로 바꿔 비교하거나 하는 방식으로.

이렇게 편리하게 사용 할 수 있는게 장점이지만, 처음에 선언 된 변수의 타입을 강제로 바꾼다는 것 때문에 위험성도 분명히 존재한다. 

 

 

 

 

0. C에서의 casting.

 

C에서의 casting은 매우 위험하다.

int main() {
	double d = 3.2;
	int* p = (int*)&d;

	printf("%lf, %d", d, *p);
	return 0;
}

이러한 타입이 아예 다른 변수도 무리없이 캐스팅 되고,

 

 

 

#include <iostream>

int main()
{
	const int c = 10;

	int* p1 = (int*)&c;
	*p1 = 20; 

	return 0;
}

이런 식으로 const 로 선언한 것도 무시하고 값을 변경할 수 있다.

 

 

 

그러다 보니 프로그래밍을 하는 데에 있어서 자유도는 매우 높아졌지만, 그에 반해 안정성이 매우 떨어지게 되어 C++에서는 이를 보완하기 위해 네 가지의 cast를 새로 만들었다.

C++에서 변수의 타입을 변경하는 데 총 네 가지가 있다.

 

static_cast

dynamic_cast

const_cast

reinterpret_cast

 

 

이 내 가지 type cast 방법의 차이를 예시 코드와 함께 알아보자.

 


1. static_cast

C++에서 가장 많이 사용하게 될 기본 casting이다.

static_cast는 컴파일 타임에 타입을 검사하고 casting을 수행한다.

 

위의 코드를 기준으로 변경해 보면

 

이런 식으로 아예 못하게끔 빌드 에러가 난다.

static_cast에 대한 건 쉽다. 위험하지 않고 논리적으로 맞는 경우에만 static_cast가 가능하고, 서로 다른 타입의 주소는 casting이 불가능하다. 

int main() {
	double d = 3.2;
	int i = static_cast<int>(d);

	enum class closeOpen{ close =0, open};
	auto eco = closeOpen::open;
	char c = static_cast<char>(eco);

	return 0;
}

 

이런 식으로, 사실 우리가 하려는 엥간한 casting들은 이 static_cast로 커버가 가능하다.

일단 내가 타입을 변경할 게 있다면 static_cast 를 통하여 type casting을 해 보면 된다. 만약 error가 없다면 그대로 사용하면 되고, 에러가 뜬다면 '내가 의도한 게 이게 맞나?' 를 진지하게 생각해 보자.

 

 

 

2. dynamic_cast

dynamic_cast도 static_cast와 거의 동일하다. 하나 다른게 있다면 static_cast는 컴파일 타임에 미리 검사하는 데 반면, dynamic_cast는 runtime에 타입을 검사하고 캐스팅을 수행한다. 그러다 보니 웬만한 상황에서는 static_cast으로 사용하는 게 낫지만, polymorphism. 다형성을 이용하는 코드에서는 어쩔 수 없이 dynamic cast를 사용하게 된다.

 

#include <iostream>

class Animal
{
public:
	virtual ~Animal() {}
	int age{0};
};
class Cat : public Animal
{
};
class Dog : public Animal
{
public:
	int height{10};
};

void NewYear(Animal* p)
{
	p->age = p->age + 1;
	/*
	    Dog 일 때만 height를 1 더 증가시키고 싶다.
		--> ??
	*/

}
int main()
{
	Animal a;
	Dog d;
	Cat c;
}

 

예를 들어, 위와 같은 코드가 있고 NewYear에 dog이 들어왔을 때만 height를 증가시키고 싶다고 하자.

만약 위의 말을 따라서 static_cast로 변경을 한다고 하면

 

 

 

void NewYear(Animal* p)
{
	p->age = p->age + 1;

	Dog* pdDog = static_cast<Dog*>(p);
	pdDog->height = pdDog->height + 1;
	std::cout << pdDog->height << std::endl;
}
int main()
{
	Animal a;
	Dog d;
	Cat c;

	NewYear(&a);
	NewYear(&d);
	NewYear(&c);
}

 

이런 식으로 모든 컴파일 시간에 모든 Animal들을 싹 다 dog으로 변경시키므로, 프로그램이 제대로 돌지 않게 된다.

 

이 때 런타임에 타입 검사를 하는 dynamic_cast를 사용한다면

 

void NewYear(Animal* p)
{
	p->age = p->age + 1;

	Dog* pdDog = dynamic_cast<Dog*>(p);

	if (pdDog == nullptr) {
		std::cout << "It is not dog ptr" << std::endl;
	}
	else {
		pdDog->height = pdDog->height + 1;
		std::cout << pdDog->height << std::endl;
	}
}
int main()
{
	Animal a;
	Dog d;
	Cat c;

	NewYear(&a);
	NewYear(&d);
	NewYear(&c);
}

 

 

실제 Dog ptr이 아니라면 dynamic_cast 반환값이 nullptr이므로 이런 식으로 원하는 결과를 얻을 수 있다.

다만 이런 경우가 아니라면 엥간하면 static_cast를 사용하는게 낫다.

 

 

 

 

3. const_cast

const_cast는 해당 변수의 const 를 제거해 주는 역할을 한다.

맨 처음 예시에서 봤었던 const 무시하고 넣는 코드. 해당 코드를 static_cast로 바꾸면 동작하지 않는다.

 

 

다만 const_cast를 통하여, const를 없애겠다고 명시를 하면 casting이 가능해진다.

 

 

 

4. reinterpret_cast

reinterpret_cast는 두 가지의 용도로 사용된다.

 

1. 서로 다른 타입간의 주소 캐스팅.

2. 정수 <==> 주소 간의 캐스팅.

 

예시와 static_cast 예를 들 때 사용했던 코드를 다시 가져와보면

 

첫 번째 p1은 c 형태로 캐스팅 한 것이니 성공한다.

다만 두 번째의 p2는 서로 다른 타입의 주소는 casting이 불가능하다는 static_cast의 설명처럼 에러가 뜬다.

하지만 서로 다른 타입간의 주소 캐스팅을 하는 reinterpret_cast를 사용하면

 

강제로 타입을 변경 할 수 있다.

 

 

또한, 값을 강제로 주소로 변경하는 것도 가능하다.

이런 식으로.

다만, reinterpret_cast 는 주소 타입 캐스팅만을 위해 있는거다. 즉 일반적인 값 캐스팅은 불가능하다.

 

 


정리

 

 

C 에서 type casting은 굉장히 자유도가 높았다. 다만 그에 따라 안전상의 문제도 분명히 존재하였다. 이를 보완하기 위해 C++에서는 네 개의 type cast 방법을 새로 만들었다.

 

 

static_cast : 일반적인 casting. compile time에 타입 검사 및 타입 변환을 수행한다. 위험하지 않고 논리적으로 맞는 경우에만 static_cast가 가능하고, 서로 다른 타입의 주소는 casting이 불가능하다. 

 

dynamic_cast : static_cast와 기본적인 개념은 동일하다. 다만 runtime에 타입 검사 및 타입 변환을 수행한다.

 

const_cast : 타입의 상수성. 즉 const를 제거하기 위하여 사용한다.

 

reinterpret_cast : 서로 다른 주소 타입간 casting을 위하여 사용된다. 또 정수 값을 주소로 강제로 변환할 수도 있다. 하지만 일반적인 값 casting은 불가능하다.

 

 

 

 

 

사실 하나하나 따져보면... C 스타일의 casting에서 할 수 있는 건 이 네 가지의 캐스팅 가지고 전부 가능하다. 다만 C 스타일처럼 하다 보면 내가 미처 확인하지 못하고 실수로 넘기는 부분이 분명 있을 수도 있다. C++에서는 이 네 가지의 type casting 방법을 제공하여, 프로그래머로 하여금 한번 더 확인하고 캐스팅을 하게끔 도와주어 안전한 프로그래밍을 할 수 있도록 한 것 같다.

반응형