프로그래밍 하다 보면 내가 원하든, 원하지 않든 자연스래 타입을 변경하여 사용할 때가 있다. 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 방법을 제공하여, 프로그래머로 하여금 한번 더 확인하고 캐스팅을 하게끔 도와주어 안전한 프로그래밍을 할 수 있도록 한 것 같다.
'C++ > 기타' 카테고리의 다른 글
[C++] Statement vs Expression (0) | 2023.06.25 |
---|---|
[C++] 함수에 const 한정자 (0) | 2023.06.15 |
[C++17] if constexpr (1) | 2023.05.17 |
[C++] LValue vs RValue (3) (1) | 2023.05.15 |
[C++] LValue vs RValue (2) (1) | 2023.05.10 |