티스토리 뷰

반응형

캐스팅은 절약, 또 절약! 잊지 말자.

책에 있는 예제들은 소스 코드가 이해하기 힘든 관계로 퍼오거나 직접 만들었습니다.
하지만 책에 있는 예제들 전부 빼놓지 않고 넣어 놨습니다 우선 쉬운 예로 시작하다가 책의 예제로 이어 가겠습니다.
책에 있는 내용을 추가 한거라고 생각 하시면 됩니다.

 

 

복습

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
(1) 구형 스타일의 캐스트(C 스타일의 캐스트)
    (1.1) 변수 캐스트 방법
        //----------------------------------------------------------------------------
        int main(void)
        {
            char *str=”ABCDE”;
            int *pi;
            pi=(int*)str;
            printf(“%d\n”,*pi);
            return 0;
        }
 
        //----------------------------------------------------------------------------
        //코드 내용
        char* str 이 가리키는 번지를 int* pi로 캐스트한후 그 번지를 읽고 있다.
        예로 출력된 값이 1701998443 이라면 그 값은 "1701998443" 그 의미를 짐작하기 어렵다.
        즉,논리적인 연관성을 찾기 힘들다. 문자열은 문자열로 읽을때만 의미가 있으며 정수형으로 읽어서는 
        값의 실용성을 찾기 어렵다. C는 의미없는 타입변환까지도 허용하여 실수를 했을 때 엉뚱한 결과가 나와도 방치한다.
        때로는 캐스트 연산자로 인한 강제 타입 변환으로 프로그램의 안전성이 위협받기도 한다.
 
    (1.2) 함수 캐스트 방법
 
        //----------------------------------------------------------------------------
        int WndMFC1(void* val);
        int main(void)
        {
             char* ch=”ABCD”;
             printf(“%c”,WndMFC1(&ch));
             return 0;
        }
        int WndMFC1(void* val)
        {
             return *( (int*)& val );
        }
        
        //----------------------------------------------------------------------------
        // 
        이럴경우 사용자는 문자열을 출력하길 원했지만 디버깅시 검사가 안나오고 이상한 값이 나옴.
        C 언어의 캐스트 연산자는 확실히 너무 무책임하고 개발자에게 모든걸 떠 넘긴다.
        원하는 대로 바꿔 줄 테니 결과가 어찌 되든 개발자가 책임을 지라는 식이다.
        그래서 C++에서는 좀더 안전하고 변환 목적에 맞게 골라 쓸수 있는 4개의 새로운 캐스트 연산자를 제공한다.
        이 연산자들은 C의 캐스트 연산자에 비해 규칙이 다소 엄격해 실수를 줄일 뿐만 아니라 
        어떤 의도의 타입 변환인지를 좀더 분명히 표시하는 장점이 있다.
 
(2) C++ 스타일의 캐스트
    (2.1) 변수 캐스트 방법 첫번째.
        (2.1.1const_cast       //const_cast<T>(표현식)
            //----------------------------------------------------------------------------
            이 캐스트 연산자는 포인터의 상수성만 변경하고 싶을 때 사용합니다.
            ★포인터의 상수성이란?
                void main()
                {
                    char str[]=”abcde”;      //선언되었을 경우
                    const char* p=str;       //p가 가리키는 주소는 변경 되지만 p가 가리키는 곳의
                    //못바꿈(주소의 비상수성,데이터의 상수성)
                    char* const p=str;       //1과 정반대
                    const char* const p=str; //1&2의 특징을 가짐(영원 불멸의 포인터~~)
                }
 
 
            const_cast외에 다른 캐스트 연산자는 포인터의 상수성을 변경시킬수 없다.
        
            //----------------------------------------------------------------------------
            // const_cast 예제1
            void main()
            {
                char str[10]=”yongmook”;
                const char* c1=str;
                char *c2;
                c2 = const_cast<char*>(c1);
                c2[0]=’g’;
                printf(“%s”,c2);
            }
            //----------------------------------------------------------------------------
            //
            c1은 str을 별다른 제약없이 대입받을수 있다.
            c2(비상수 지시 포인터)는 c1(대입받은 포인터이며 상수 포인터)을 대입받고자 할 때 c2=c1은 안된다.
            두 포인터의 상수성이 다르며 c1이 가리키는 값을 c2로 바꿀 가능성이 있기 때문이다.
            그러나 위 코드의 경우 최초 str이 변경 가능한 대상이라는 것을 확실히 알고 있으므로 c1의 상수성만 잠시 무시하면 대입이 가능하다.
            이때 const_cast 연산자로 c1을 char*로 캐스팅할수 있다.
            하지만 str이 배열이 아니라 포인터로 선언되어 있다면 이때 str은 첫번째 문자열의 주소번지를 가리키고 있으므로 
            즉, 문자열의 일부분을 가리키고 있으므로 변경할수 없다.
            만약 위에 상태라면 포인터의 상수성을 함부로 변경하면 위험해진다.
 
            //----------------------------------------------------------------------------
             // const_cast 예제2
            void main()
            {
                const int i=10;
                double d=const_cast<double>(i);
            }
 
            //---------------------------------------------------------------------------- 
            //
            보통 정수를 실수형 타입으로 변환하는 것은 당연히 할수 있지만 const_cast는 이것조차도 허용하지 않는다.
            묵시적 변환 대입인데도 말이다!!
            이처럼 const_cast는 오로지 포인터의 상수성만 변경할수 있다.
            상수성을 변경할 때 이 캐스트 연산자를 사용하면 다른 엉뚱한 변환을 피할 수 있어 더 안전하며 
            코드를 읽는 사람도 어떤 의도로 이캐스트 연산자를 사용했는지 쉽게 파악할수 있다.
 
    (2.2) 변수 캐스트 방법 두번째.
        (2.2.1dynamic_cast             //dynamic_cast<T>(표현식)
            포인터끼리 또는 레퍼런스끼리 변환하는데 반드시 포인터는 포인터로 변환해야 하고 레퍼런스는 
            레퍼런스로 변환할 때 사용한다. (상식적으로 포인터를 레퍼런스로 바꾸거나 레퍼런스를 포인터로 변환한다는 것은 필요하지도 않고 불가능하다.)
            포인터끼리 변환 할때도 반드시 상속 계층에 속한 클래스 끼리만 변환할 수 있다.
            부모자식간을 변환할 때 업 캐스팅은 원래부터 허용되는 것이므로 이 캐스트 연산자가 있으나 없으나 당연한 기능이다.
            단,문제는 부모타입의 포인터를 자식 타입의 포인터로 다운 캐스팅할 때인데 이때는 무조건 변환을 호용하지 않고 안전하다고 판단될 때만 허용한다.
        
            //----------------------------------------------------------------------------
             // dynamic_cast 예제 
            class Human
            {
            public:
                virtual void Upcasting();
            };
 
            class Student:public Human 
            {
            public:
                virtual void Upcasting();
            };
 
            void main()
            {
                Human* ab= new Student;           //업캐스팅…ab.Upcasting() 은 학생의 함수 호출.
                Student* ac= (Student*) ab;        //다운캐스팅.. ac. Upcasting()은 학생의 함수 호출.
                //ab가 타입이 틀려도 student를 가리키고 있으므로 가능..
            }   
                                                
            하지만 ab가 자기자신만 가리키고 있는 상황이라면
 
            void main()
            {
                Human ad, *ae;
                Student *af;
                ae=&ad;             // Human..
                af= (Student*) ae;// 다운 캐스팅 불가능
            }
 
            부모(Human) 객체를 다운 캐스팅해서 자식 객체를 가리키는 포인터에 대입한후 이포인터로
            자식에게만 있는 멤버를 참조할 수도 있기 때문에 위의 다운 캐스팅은 불가능하다.
 
            dynamic_cast 연산자는 이럴 경우 캐스팅을 허용하지 않고 NULL을 리턴하여 위험한 변환을 허가하지 않는다.
 
            위 코드의 dynamic_cast 으로 바꿀 경우
            void main()
            {
                //af= (Student*) ae;
                af= dynamic_cast<Child*>ae;
                //af=00000000//(NULL) 이 출력될 것이다
            }
 
            안전한 객체의 번지에 대해서는 제대로 다운 캐스팅을 하고 그렇지 않을 경우에는 캐스팅을 거부한다.
            (static_cast도 상속관계에 있는 클래스들을 캐스팅한다는 점에 있어서 기능상 동일 하지만 
            다운 캐스팅을 할 때 static_const는 무조건 변환을 허가 하지만 
            dynamic_cast는 실행중에 타입을 점검하여 안전한 캐스팅만 허가한다는 점이 다르다.)
 
            dynamic_cast는 실행중에 객체의 실제 타입을 판별해야 하기 때문에 
            dynamic_cast을 사용할려면 RTTI옵션이 켜져 있어야하며 변환대상 타입들끼리는 상속 관계에 있어야 하고 
            최소한 하나 이상의 가상 함수를 가져야 한다.
    
    (2.3) 변수 캐스트 방법 세번째
        (2.3.1reinterpret_cast        // reinterpret_cast <T>(표현식)
            reinterpret_cast 연산자는 임의의 포인터 타입끼리 변환을 허용하는 상당히 위험한 캐스트 연산이다.
            정수형값을 포인터 타입으로 바꾸어 절대 번지를 가리키도록 한다거나 할 때 사용한다.
 
            //---------------------------------------------------------------------------------------
            //reinterpret_cast 예1
            void main()
            {
 
                int *pi;
                char *pc;
                
                //12345라는 정수값을 정수형 포인터로 바꿈
                pi= reinterpret_cast<int*>(12345);   
                //이 값을 다시 문자형 포인터로 바꿈      
                pc= reinterpret_cast<char*>(pi);         
            } 
 
            대입은 가능하지만 이후 포인터를 사용해서 발생하는 문제는 전적으로 프로그래머의 책임이다.(일종의 강제 변환으로 안전하지 않고 이식성도 없다)
            reinterpret_cast 는 포인터 타입간의 변환이나 포인터와 수치형 데이터의 변환에만 사용하며 기본 타입들끼리의 변환에는 사용할수 없다.
 
            //---------------------------------------------------------------------------------------
            //reinterpret_cast 예2
            void main()
            {
                int pi=20;
                double pd;
                 //기본타입…불가능 하지만 static_cast 연산자는 가능
                pd= reinterpret_cast< double>(pi);                    
            }
 
    (2.4) 변수 캐스트 방법 네번째.
        (2.4.1static_cast      // static_cast<T>(표현식)
            static_cast 연산자는 지정한 타입<T>으로 변경하는데 무조건 변경하는 것이 아니라 논리적으로 변환 가능한 타입만 변환한다.
 
            //---------------------------------------------------------------------------------------
            //static_cast 예1.(클래스가 아닐경우)
            void main()
            {
                char *str=”abcde”;
                int *pi;
                double d=123.456;
                int i;
 
                i=static_cast<int>(d);           //가능
                pi= static_cast<int*>(str);     //불가능
                pi=(int*)str;                        //가능(C스타일)
            }
            실수형의 d를 정수형으로 캐스팅은 허용된다.
            그러나 포인터의 타입을 다른 것으로 변환하는 것은 허용되지 않으며 컴파일 에러로 처리된다.
            위험한 캐스트 연산을 컴파일 중에 알려 줌으로써 실수를 방지할 수 있다.(C스타일로는 컴파일 에러가 나오게 하는건 불가능!너무 친절하기 때문에~~)
            포인터끼리 타입을 변환할 때는 상속 관계(클래스)에 있는 포인터끼리만 변환이 허용되며 상속 관계가 아닌 포인터끼리는 변환을 거부함.
 
            //---------------------------------------------------------------------------------------
            //static_cast 예2.(클래스일 경우)
            class Parent{ };
            class Child : public Parent { };   //상속… static_cast ok.
            void main()
            {
                Parent P, *pP;
                Child C, *pC;
                
                int i=1;
                pP=static_cast<Parent*>(&C);     //업 캐스팅 ….가능
                pC= static_cast<Child*>(&P);     //다운 캐스팅..가능하지만 위험.<-특징~!
                pP= static_cast<Parent*>(&i);    //불가능
                pC= static_cast<Child*>(&i);     //불가능
 
            }
            정수형 포인터 상수 &i를 Parent*나 Child* 타입으로 변환하는 것은 안된다.
            int는 Parent 나 Child와 상속 관계에 있지 않기 때문이다.
            (pP= static_cast<Parent*>(&i); 문장이 허용될경우 pP로 Parent의 멤버 함수를 호출할수 있을 텐데 정수형 변수 i는 이런 멤버 함수를 가지고 있지 않다.당연함….)
            pC= static_cast<Child*>(&P);의 경우 부모(Parent) 객체가 자식(Child) 객체의 모든 멤버를 가지고 있지 않으므로 위험한 변환이다.(static_cast는 실행중에 타입 체크를 하지 않으므로 
            이 변환이 위험하다는 것까지는 모르므로 일단 허용한다 다이나믹 캐스트는 이런 경우를 막아준다
 
 
 
 
 
 
 
 
cs

○복습-결론

캐스트 연산자

변환 형태

const_cast

const,volatile 등의 속성 변경

dynamic_cast

reinterpret_cast static_cast

상속 관계의 클래스 포인터 및 레퍼런스,타입 체크,RTTI 기능 필요

포인터끼리,포인터와 수치간의 변환

상속 관계의 클래스 포인터 및 레퍼런스,기본타입,타입 체크 안함

 

결론

   1.다른 방법이 가능하다면 캐스팅은 피하십시오.특히 수행 성능에 민감한 코드에서 dynamic_cast 는 몇 번이고 다시 생각하십시오. 설계 중에 캐스팅이 필요해졌다면, 캐스팅을 쓰지 않는 다른 방법을 시도해 보십시오.

2. 캐스팅이 어쩔 수 없이 필요하다면, 함수 안에 숨길 수 있도록 해 보십시오. 이렇게 하면 최소한 사용자는 자신의 코드에 캐스팅을 넣지 않고 이 함수를 호출할 수 있게 됩니다.

3.구형 스타일의 캐스트를 쓰려거든 C++스타일의 캐스트를 선호하십시오.발견하기도 쉽고,설계자가 어떤 역할을 의도 했는지가 더 자세하게 드러납니다.

 

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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
글 보관함