bdfgdfg
[C++] C++의 4가지 캐스팅(형변환) 본문
캐스팅(형변환)
- 하나의 데이터형을 다른 데이터형으로 변환하는 것.
기존의 C스타일의 형변환은 밑과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Test
{
private:
int m_test = 10;
};
int main()
{
int a = 5;
long long b = a; // 묵시적 형변환. 컴파일러가 형을 변환해준다.
// -> 형변환이 허용되며 프로그래머가 명시적으로 형변환을 안한경우.
Test* t = (Test*)&a; // 명시적 형변환. 프로그래머가 형변환을 위한 코드를 직접 작성.
return 0;
}
|
cs |
C++스타일의 형변환 연산자는 4가지가 존재한다.
1. static_cast
2. reinterfret_cast
3. const_cast
4. dynamic_cast
C스타일의 캐스팅은 C++의 캐스팅 연산자 4개중 하나를 한다.
-> 어떤 형변환인지 명확하지 않다. (형변환의 의도를 구별하기 힘들다)
-> 명백한 실수를 컴파일러가 캐치하지 못할수도 있다.
그렇기에 좀 더 안전하고, 사용자의 의도를 좀 더 나타내기 위한 C++ 스타일의 형변환을 사용한다.
static_cast
정적 캐스트. 보통 정적이라 함은 언어에서 컴파일시에~ 동적이라함은 런타임시에~를 의미한다.
즉 static_cast 연산자를 통해 형변환을 하면 컴파일(정적) 타임에 형변환이 가능한지 검사한다.
기본 자료형간의 형변환도 허용하며, 이 때 최대한 값을 유지.
-> 이진수 표기또한 달라질 수 있다. ((float -> int등)
1
2
3
4
|
float a = 50.545f;
int b = 60;
b = static_cast<int>(a);
std::cout << b; // 50이 출력된다.
|
cs |
위의 C스타일 캐스팅의 예제에서 Test 포인터 변수에 강제로 int형 변수의 주소를 Test*로 형변환하여 대입했다.
이것을 static_cast를 이용하여 형변환이 가능한지 실험해보면.
위와 같이 컴파일 에러를 내뱉는다.
- 컴파일 에러(컴파일러가 이해하지 못하는 코드를 발견할 때 발생하는 오류)
이렇게 포인터 타입을 다른것으로 변환 하는것을 허용하지 않는다.(컴파일 에러 발생)
다만 상속 관계에 있는 클래스 타입의 형변환은 허용한다.
- 부모 클래스의 참조/포인터 형식에서 자식 클래스의 참조/포인터 형식으로의 형변환 허용.(unsafe)
-> 다운캐스팅이라 한다.(부모 -> 자식으로 형변환)
- 반대로 자식 클래스의 참조/포인터 형식에서 부모 클래스의 참조/포인터 형식으로의 형변환 허용.(safe)
-> 업캐스팅이라 한다.(자식 -> 부모로 형변환)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class Base
{
public:
void Print()
{
std::cout << "I'm Base " << m_base << std::endl;
}
private:
int m_base = 50;
};
class Derived : public Base
{
public:
void Print()
{
std::cout << "I'm Derived " << m_derived << std::endl;
}
private:
int m_derived = 100;
};
int main()
{
Base* b;
Derived* d = new Derived();
// 업 캐스팅.(더 작은 메모리의 부모가 더 큰 자식의 메모리를 가르킨다. - safe)
b = static_cast<Base*>(d);
b->Print();
delete d; return 0;
}
|
cs |
결과.
반대로 다운 캐스팅을 보면.
1
2
3
4
5
6
7
8
9
|
int main()
{
Base* b = new Base();
Derived* d;
// 다운 캐스팅.(더 큰 메모리의 자식이 더 작은 부모의 메모리를 가르킨다. - unsafe)
d = static_cast<Derived*>(b);
d->Print();
delete b;
}
|
cs |
이상한 쓰레기값이 출력된다.
-> 부모는 자식 클래스의 멤버를 가지고 있지 않기에 크래시가 날수도 있음.
즉 이렇게 static_cast는 형변환의 허용범위가 넓지만 상속관계에서의 형변환은 그다지 안전하지 않다.
static_cast연산자는 일반적으로 4가지 형변환 연산자중 가장 사용빈도가 높다.
- 다른 형변환 연산자와는 달리 기본 자료형의 형변환을 허용하므로
- 밑에서 설명할 dynamic cast는 기본 자료형간의 형변환이 불가능하고 static_cast에 비해 성능이 떨어짐.
- reinterpret_cast와 const_cast는 사용예제가 다름.
reinterpret_cast
이 형변환 연산자는 일반적으로 허용하지 않는 형변환을 강제적으로 형변환할 때 사용한다.
- 포인터/참조와 관련된 형변환만 지원한다.
- 연관없는 두 포인터 형 사이의 변환을 허용
- 이름에서 알 수 있듯이 말 그대로 재해석(reinterpret)한다고 볼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Animal
{
private:
int m_age;
};
class Human
{
private:
int m_age;
};
int main()
{
Animal* animal = new Animal();
Human* human = new Human();
human = static_cast<Human*>(animal); // 컴파일에러.
human = reinterpret_cast<Human*>(animal); // 형변환 허용!
delete animal;
delete human;
}
|
cs |
위와 같이 전혀 관계가 없는 클래스간 포인터타입으로의 형변환도 허용한다.
의미는 간단하지만 잘못 사용할시 쉽게 문제가 발생할 수 있는 형변환 연산자.
이런 형변환 연산자가 사용되는 경우는 가끔 라이브러리의 함수의 자료형을 형변환 할때가 있는데 그럴때 자주 사용된다. 예시를 보면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
struct LibMatrix4x4 // 어떤 라이브러리 구조체
{
float _11, _12, _13, _14;
float _21, _22, _23, _24;
float _31, _32, _33, _34;
float _41, _42, _43, _44;
};
struct MyMatrix // 사용자 정의 구조체
{
float m[4][4];
};
int main()
{
LibMatrix4x4 libMatrix;
float* a = (float*)&libMatrix;
for (int i = 0; i < 16; ++i)
*(a + i) = (i + 1.0f); // 1 ~ 16
}
|
cs |
위와 같이 불편하게 하나하나 접근해야 하는 구조체의 멤버를 사용자가 정의한 행렬 구조체로 형변환하여 쉽게 사용가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
int main()
{
LibMatrix4x4 libMatrix;
float* a = (float*)&libMatrix;
for (int i = 0; i < 16; ++i)
*(a + i) = (i + 1.0f); // 1 ~ 16
MyMatrix* matrix = reinterpret_cast<MyMatrix*>(&libMatrix);
for (int y = 0; y < 4; ++y)
{
for (int x = 0; x < 4; ++x)
std::cout << matrix->m[y][x] << " ";
std::cout << std::endl;
}
return 0;
}
|
cs |
결과
잘못사용하면 위험한 형변환 연산자이지만 위의 간단한 예시처럼 유용하게 사용될일도 존재한다.
const_cast
- 이 형변환 연산자는 다른 자료형으로 바꿀수는 없다.
- const 또는 volatile이 붙은 자료형의 속성을 제거할 때 사용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int main()
{
const char* any = "Hello";
char* temp = const_cast<char*>(any);
Animal* myAnimal = new Animal();
const Animal* cstPtr = myAnimal;
Animal* ptr = const_cast<Animal*>(cstPtr);
delete myAnimal;
return 0;
}
|
cs |
가장 간단한 개념이지만,
const라는 의미는 객체가 변하지 말아야한다는 코드작성자의 의도를 컴파일러와 다른 사람에게 알려주는 유용한 수단이므로 const_cast를 이용하여 const 속성을 제거하는건 옳지 못하다.
다만 라이브러리의 함수 인자가 const기능이 없고 const 속성이 존재하는 변수를 넘기지 못하는 경우.
1
2
3
4
5
6
|
void LibFunc(char* ptr) { ~~~ }
void MyFunc(const char* ptr)
{
LibFunc(const_cast<char*>(ptr));
}
|
cs |
(간단한 예시)
위와 같은 예시가 아니라면 const를 해제하는 일은 가급적 자제해야 한다.
dynamic_cast
- static_cast와 달리 컴파일 시점이 아닌 런타임시에 형변환을 검사하여 형변환을 보다 안전하게 처리한다.
-> 실행중에(런타임) 자료형을 판단
-> 포인터 또는 참조형을 캐스팅할 때만 사용가능.
-> 정확히는 부모 클래스와 자식 클래스간의 변환에 사용된다. (실패시 0을 반환)
-> 하나 이상의 virtual함수가 존재해야 한다.
-> 호환되지 않는 자식형으로 캐스팅하려하면 NULL반환 (다운 캐스팅 SAFE)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
class Animal
{
public:
Animal() { }
virtual ~Animal() { }
};
class Cat : public Animal
{
public:
Cat() { }
~Cat() { }
};
class Dog : public Animal
{
public:
Dog() { }
~Dog() { }
};
int main()
{
Animal* cat = new Cat();
Dog* dog = dynamic_cast<Dog*>(cat);
delete cat;
}
|
cs |
위와 같이. cat객체를 생성하고 그 객체를 Animal타입이 가르키고 있을 때 자식으로 형변환하는 경우.
cat포인터 변수가 가르키고 있는것은 Cat객체. dynamic_cast를 통해 Dog*로 형변환할 시
NULL을 반환한다.
제대로 Cat으로 형변환을 할 시
이렇게 형변환이 안전하게 가능해진다.
하지만 이게 가능한 이유는 RTTI(실시간 타입 정보)기능 덕분인데
성능을 중요시하는 c++에서는 속도의 저하를 유발하는 RTTI는 끄는게 보통이라 한다.
'게임프로그래밍 > C++' 카테고리의 다른 글
[C++11] default,delete,final,override 키워드 (0) | 2022.01.22 |
---|---|
[C++ 11] auto,nullptr (0) | 2022.01.19 |
[C++] 템플릿 (0) | 2022.01.16 |
[C++] 연산자 오버로딩 (0) | 2022.01.15 |
[C++ 객체지향] 다형성(Polymorphism) (0) | 2022.01.09 |