C++ 캐스팅(cast)에는 총 4가지 종류가 있습니다.
바로
static_cast
reinterpret_cast
const_cast
dynamic_cast
입니다.
각각 무슨 기능을 하는지 알아 보자면
static_cast |
명시적 형 변환을 위한 캐스트 연산자 |
reinterpret_cast |
무조건 적으로 변환한다.(강제 변환) ( reinterpret 다시 해석하다, 새로 해석하다.) 메모리 단위로 분해하여 재조립합니다. 굉장히 위험한 방법입니다. |
const_cast |
const 즉 상수성을 제거 할때 사용됩니다. |
dynamic_cast |
RTTI( Run Time Type Information )를 위한 캐스팅 입니다. |
하지만, 굳이 귀찮게 위의 방법을 써야하는 이유가 뭘까요?
저희가 C언어 쓸때는 그냥 (Type) 으로 캐스팅 연산을 했었습니다.
다음과 같은 경우를 봅시다.
1 2 3 4 5 6 7 8 9 10 11 | #include <iostream> int main() { int m_4byte = 0; double* m_pointer = (double*)&n; *m_pointer = 1.23; return -1; } | cs |
무엇이 문제 인지 보이시나요?
위와 같은 방법을 사용하면 어떻게 될까요?
에러가 납니다. 직접 VS를 열어서 빌드해보세요.
그럼 다음 경우를 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <iostream> int main() { const int c_int = 50; int * p_int = (int*)&c_int; *p_int = 100; cout << c_int << endl; cout << *p_int << endl; return -1; } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <iostream> int main() { int value = 50; const int c_int = value; int * p_int = (int*)&c_int; *p_int = 100; cout << c_int << endl; cout << *p_int << endl; return -1; } | cs |
이 나오게 됩니다.
이는 const의 값이 결정되는 시점에 따라 달라집니다만, 그건 나중에 const에 관련 글을 적을때 적도록 하겠습니다.
이 2개의 코드로 알아야 하는것은 C에서 지원하는 cast 방식은, 불안전 하다는 것입니다.
2개의 코드 다 에러를 나타내지 않습니다. 하지만 비정상적인 결과를 출력하죠.
과연 이게 올바른 걸까요?
그럼 static_cast로 변경을 해봅시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <iostream> int main() { int value = 50; const int c_int = value; int * p_int = static_cast<int*>(&c_int); *p_int = 100; cout << c_int << endl; cout << *p_int << endl; return -1; } |
컴파일러는 const 또는 다른 형식 한정자로 캐스팅 할수없습니다. 라며 에러를 띄우고 빌드를 하지 않습니다.
불안전 하기 때문이죠.
만약 const값을 변하게 하려는 의도가 있다면, const_cast를 사용해야 할것입니다.
그렇기에 안정하고 정상적인 캐스팅을 위하면 C++에서는 기본적으로 명시적캐스팅인 static_cast를 사용하도록 합시다.
다음은 reinterpret_cast 입니다.
reinterpret_cast는 제가 위에 이렇게 적어 놨을겁니다.
무조건 적으로 변환한다.(강제 변환) ( reinterpret 다시 해석하다, 새로 해석하다.) 메모리 단위로 분해하여 재조립합니다. 굉장히 위험한 방법입니다.
여기서 중요하게 보셔야 할 부분은 '메모리' 단위 입니다.
메모리 단위란 무엇일까요?
그럼 이 코드를 자세히 봅시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <iostream> class A {public : int a;}; class B {public : int b;}; class C : public A, public B { int c; }; int main() { C c; return -1; } | cs |
위의 코드에서 class C는 class A와 B를 다중 상속 받은 상태입니다. 그리고 A와 B는 각각 4byte짜리의 int를 가지고 있죠.
그럼 C를 선언 하는 순간 메모리는 어떻게 잡힐까요?
Class C |
Class A |
int a; | 4 Byte |
Class B |
int b; | 4 Byte | |
C클래스 멤버 |
int c; | 4 Byte |
다음과 같이 구조를 가질껍니다.
그럼 위와 같은 메모리 구조를 가졌을때 아래와 같이
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <iostream> class A {public : int a;}; class B {public : int b;}; class C : public A, public B { int c; }; int main() { C c; A* p_a = &c; cout << &c << end; cout << p_a << end; return -1; } | cs |
코드를 작성 한후 실행하면 2개의 결과 값은 똑같이 나올겁니다.
하지만 이 코드를 어떨까요?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <iostream> class A {public : int a;}; class B {public : int b;}; class C : public A, public B { int c; }; int main() { C c; B* p_b = &c; cout << &c << end; cout << p_b << end; return -1; } | cs |
C클래스가 부모인 B클래스로 암시적 캐스팅이 된 형태 입니다.
실행 하면 결과는
------------------------------------
&c의 주소값
&c의 주소값 + 4byte
------------------------------------
암시적 변환으로 메모리의 주소값이 바뀌었습니다.
물론 자식 -> 부모 변환이라 static_cast를 사용하셔도 됩니다.
하지만 reinterpret_cast를 사용하면 어떻게 될까요?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <iostream> class A {public : int a;}; class B {public : int b;}; class C : public A, public B { int c; }; int main() { C c; B* p_b = reinterpret_cast<B*>(&c); cout << &c << end; cout << p_b << end; return -1; } | cs |
놀랍게도 2개의 출력값이 같은 주솟값을 가지게 됩니다.
메모리 단위로 분해해서 재 생성 하기 때문이죠.
이와 같은 방법을 전 아직 어디서 잘 사용해야 할지 모르겠습니다. ( 나중에 알게되면 꼭 수정하겠습니다. )
하지만 저기서 p_b가 가진 멤버변수인 b를 수정하게 되면 A클래스가 가진 a가 수정이 된답니다.
Class C | Class A | int a; | 4 Byte |
Class B | int b; | 4 Byte | |
C클래스 멤버 | int c; | 4 Byte |
위와 같은 구조가
Class C | Class B | int b; | 4 Byte |
Class C | int c; | 4 Byte | |
( ) | (4 Byte ) |
이렇게 조립 되었을뿐 메모리 주소를 변하지 않았기 때문이죠.
전 처음 이 사실을 알았을때는 정말 신기했습니다. 잘만 사용하면 메모리 단위로 잘 쪼개서 사용 할 수 있기 때문이죠.
마지막으로 dynamic_cast입니다.
dynamic_cast는 RTTI(Runtime Type Information)를 위한 캐스팅이라고 제가 위에 적었는데요.
런타임에 상속 계츨 관계를 가로지르거나 다운 캐스팅시 사용되는 캐스팅 연산자 라고 생각하시면 편합니다.
즉, 부모가 자식으로 캐스팅이 될 수 없는데, 런타임중 판단으로 가능하게 된다는거죠.
단 dynamic_cast는 다형성을 띄지 않은 객체간의 변환은 불가능하며, 컴파일이 에러를 낸답니다.
즉 다형성을 가지려면 클래스간 virtual 멤버 함수가 있어야 합니다.
코드로 표현하면 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <iostream> using namespace std; class A { public: int a; virtual void test(){} }; class C : public A { int c; virtual void test(){} }; int main() { C c; A* pA = &c; C* c2 = dynamic_cast<C*>(pA); return -1; } | cs |
하지만 저 dynamic_cast가 올바르지 않았을경우는 null값을 반환하니 주의 하시기 바랍니다.
읽어주셔서 감사합니다
'프로그래밍 > Language' 카테고리의 다른 글
[C++] 출력시 소수점 자리 조절 (0) | 2016.11.28 |
---|---|
[C++] const에 대하여 (0) | 2016.07.28 |
[C++] Virtual 소멸자를 사용해야 하는 이유 (2) | 2016.04.20 |
[C++]오버로딩(Overloading)과 오버라이딩(Overriding)의 예제와 설명 (0) | 2016.04.14 |
[C++] 인터페이스(Interface) 예제 (0) | 2016.04.14 |