如何写优雅的代码(序)——自语
//========================================================================
//TITLE:
// 如何写优雅的代码(序)——自语
//AUTHOR:
// norains
//DATE:
// Thursday 16-July-2009
//Environment:
// WINCE5.0 + VS2005
//========================================================================
这个系列是写给追求代码优雅的偏执狂。
这基调意味着如下情形:
1.本系列不适应于初学者。至少,没有语言美感的朋友,对本系列应该不会有太大的感冒。
2.本系列对解决实际问题没有帮助。如果你不懂,看完之后你还是不懂;如果你懂了,看完之后你不会懂得更多。
以文章做个比方。我这系列,不是告诉你如何去写文章,怎样才能突出文章的中心细想,怎样才能去感动人;而是告诉你如何给文章润色,让文章显得更优雅。
仅此而已。
另,因本人实在懒散,虽名为系列,但不确保会持续连续写下去。也许今天一点,明天一些,后天空白,逐步逐步完成所谓的“系列”。
之所以为系列,仅是提醒自己而已,无它。
如何写优雅的代码(1)——灵活使用goto和__try
//========================================================================
//TITLE:
// 如何写优雅的代码(1)——灵活使用goto和__try
//AUTHOR:
// norains
//DATE:
// Thursday 16-July-2009
//Environment:
// WINCE5.0 + VS2005
//========================================================================
goto是毒药?凡是能用goto的地方,肯定能用结构化方式来实现相同的目的!估计很多朋友都对这论断不会陌生,甚至可以说,太熟悉了!但能实现并不代表优雅。不信?我们接下来看看。
假设我们有一个函数,需要实现如下功能:将一个驱动某些内容读取到缓存区去;又因为该缓存是全局公用的,所以我们很自然采用互斥量来进行控制。首先,我们坚持采用结构化方式实现,很可能我们的代码类似如下:
1 BOOL ReadDeviceBuf()
2 {
3 EnterCriticalSection(&g_csBuf);
4
5 //打开驱动设备
6 HANDLE hDev = CreateFile(TEXT("DEV1:"),
7 FILE_WRITE_ATTRIBUTES,
8 0,
9 NULL,
10 OPEN_EXISTING,
11 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
12 NULL);
13
14 if(hDev == INVALID_HANDLE_VALUE)
15 {
16 LeaveCriticalSection(&g_csBuf);
17 return FALSE;
18 }
19
20 //获取驱动设备的缓存大小
21 DWORD dwSize = 0;
22 if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
23 {
24 CloseHandle(hDev);
25 LeaveCriticalSection(&g_csBuf);
26 return FALSE;
27 }
28
29 //分配缓存
30 g_pBuf = new BYTE[dwSize];
31 if(g_pBuf == NULL)
32 {
33 CloseHandle(hDev);
34 LeaveCriticalSection(&g_csBuf);
35 return FALSE;
36 }
37
38 //从驱动中读取数据
39 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)
40 {
41 delete []g_pBuf;
42 g_pBuf = FALSE;
43
44 CloseHandle(hDev);
45 LeaveCriticalSection(&g_csBuf);
46 return FALSE;
47 }
48
49
50 CloseHandle(hDev);
51 LeaveCriticalSection(&g_csBuf);
52
53 return TRUE;
54 }
没错,采用这种结构化的方式的确是解决了问题。可是,我们是不是有点别扭呢?每次出错,返回FALSE之前,都必须要清理一次资源。小函数也许还不是什么大问题,只要睁大眼睛,小心翼翼,还是能在后续的返回中正确处理资源释放的。但如果函数因为要加入某些功能越来越大,又或许是别人来维护这段代码,那能保证在返回前释放资源么?
接下来我们使用被大家鄙弃的goto,看看会发生什么情形:
[cpp] view plaincopy
55 BOOL ReadDeviceBuf()
56 {
57 BOOL bRes = FALSE;
58
59 EnterCriticalSection(&g_csBuf);
60
61 DWORD dwSize = 0;
62
63 //打开驱动设备
64 HANDLE hDev = CreateFile(TEXT("DEV1:"),
65 FILE_WRITE_ATTRIBUTES,
66 0,
67 NULL,
68 OPEN_EXISTING,
69 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
70 NULL);
71
72 if(hDev == INVALID_HANDLE_VALUE)
73 {
74 goto EXIT;
75 }
76
77 //获取驱动设备的缓存大小
78 //DWORD dwSize = 0; //产生编译错误:initialization of 'dwSize' is skipped by 'goto EXIT'
79 if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
80 {
81 goto EXIT;
82 }
83
84 //分配缓存
85 g_pBuf = new BYTE[dwSize];
86 if(g_pBuf == NULL)
87 {
88 goto EXIT;
89 }
90
91 //从驱动中读取数据
92 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)
93 {
94 goto EXIT;
95 }
96
97 bRes = TRUE;
98
99 EXIT:
100
101 if(bRes == FALSE)
102 {
103 delete []g_pBuf;
104 g_pBuf = FALSE;
105 }
106
107 CloseHandle(hDev);
108 LeaveCriticalSection(&g_csBuf);
109
110 return bRes;
111 }
怎么样?把所有的资源释放都放到EXIT段中,每个EnterCriticalSection都能对应一个LeaveCriticalSection,是不是显得比之前的更为优雅?还能说goto为鸡肋么?
不过,goto也不是尽善尽美,比如变量dwSize在goto之后就不能初始化,只能将局部变量的初始化放到第一个goto之前。按照C++的建议,变量的声明最好尽可能接近使用的地方。而放在第一个goto之前,摆明就是C作风嘛!
其实如果以本特例,直接声明dwSize而不进行初始化也是可行的;但这并不代表在别的情况下也能畅通无阻,也许有的程序就依赖于初始化的值,谁知道呢?
那有没有更为优雅的?可以解决这dwSize的位置问题的?答案自然是肯定的。不过,就必须请我们的__try出场咯:
[cpp] view plaincopy
112 BOOL ReadDeviceBuf()
113 {
114 BOOL bRes = FALSE;
115
116 EnterCriticalSection(&g_csBuf);
117
118 //打开驱动设备
119 HANDLE hDev = CreateFile(TEXT("DEV1:"),
120 FILE_WRITE_ATTRIBUTES,
121 0,
122 NULL,
123 OPEN_EXISTING,
124 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
125 NULL);
126
127 __try
128 {
129 if(hDev == INVALID_HANDLE_VALUE)
130 {
131 __leave;
132 }
133
134 //获取驱动设备的缓存大小
135 DWORD dwSize = 0;
136 if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
137 {
138 __leave;
139 }
140
141 //分配缓存
142 g_pBuf = new BYTE[dwSize];
143 if(g_pBuf == NULL)
144 {
145 __leave;
146 }
147
148 //从驱动中读取数据
149 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)
150 {
151 __leave;
152 }
153
154 bRes = TRUE;
155
156 }
157 __finally
158 {
159
160 if(bRes == FALSE)
161 {
162 delete []g_pBuf;
163 g_pBuf = FALSE;
164 }
165
166 CloseHandle(hDev);
167 LeaveCriticalSection(&g_csBuf);
168 }
169
170 return bRes;
171 }
哦耶!现在dwSize终于在它该出现的位置上了,是不是显得比goto更为优雅呢?
这段改写的代码采用的是SEH机制。因为SEH机制如果需要详细解释,就不是一言两语的事情,所以在此就略过,感兴趣的朋友可以自己在网上查找资料。在这里,只是说明一点,采用SEH机制,无论如何,最后基本上一定要运行__finally段代码,除非中间有中断。
最后一段是不是意味着凡是可以运用goto的地方都能采用__try替代?答案是否定的。特别是代码中采用了STL,SEH机制将会无能为力。
不信?我们添加一点代码段来看看事情的真相。假设我们不是通过数组来保留缓存,而是保留于STL的vector中,并且成功读取之后,我们还想输出每个数值,那么我们代码可以如下:
[cpp] view plaincopy
172 BOOL ReadDeviceBuf()
173 {
174 BOOL bRes = FALSE;
175
176 EnterCriticalSection(&g_csBuf);
177
178 //打开驱动设备
179 HANDLE hDev = CreateFile(TEXT("DEV1:"),
180 FILE_WRITE_ATTRIBUTES,
181 0,
182 NULL,
183 OPEN_EXISTING,
184 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
185 NULL);
186
187 __try
188 {
189 if(hDev == INVALID_HANDLE_VALUE)
190 {
191 __leave;
192 }
193
194 //获取驱动设备的缓存大小
195 DWORD dwSize = 0;
196 if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
197 {
198 __leave;
199 }
200
201 //分配缓存
202 g_vtBuf.resize(dwSize);
203
204 //从驱动中读取数据
205 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)
206 {
207 __leave;
208 }
209
210
211 //打印每个数据
212 //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding”
213 for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)
214 {
215 printf("%d/n",*iter);
216 }
217
218 bRes = TRUE;
219
220 }
221 __finally
222 {
223
224 if(bRes == FALSE)
225 {
226 g_vtBuf.clear();
227 }
228
229 CloseHandle(hDev);
230 LeaveCriticalSection(&g_csBuf);
231 }
232
233 return bRes;
234 }
很遗憾,这段代码无法编译通过。因为STL的迭代器中用到了对象,而对象会释放C++异常,而这和SEH有冲突。当然,我们完全可以用new来替代,以避开这个问题,但这样一来,却是使代码更峥嵘,离优雅更是八辈子打不到一个杆子上。
这时候,还是只能用goto:
[cpp] view plaincopy
235 BOOL ReadDeviceBuf()
236 {
237 BOOL bRes = FALSE;
238
239 EnterCriticalSection(&g_csBuf);
240
241 //打开驱动设备
242 HANDLE hDev = CreateFile(TEXT("DEV1:"),
243 FILE_WRITE_ATTRIBUTES,
244 0,
245 NULL,
246 OPEN_EXISTING,
247 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
248 NULL);
249
250
251 if(hDev == INVALID_HANDLE_VALUE)
252 {
253 goto EXIT;
254 }
255
256 //获取驱动设备的缓存大小
257 DWORD dwSize = 0;
258 if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
259 {
260 goto EXIT;
261 }
262
263 //分配缓存
264 g_vtBuf.resize(dwSize);
265
266 //从驱动中读取数据
267 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)
268 {
269 goto EXIT;
270 }
271
272
273 //打印每个数据
274 //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding”
275 for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)
276 {
277 printf("%d/n",*iter);
278 }
279
280 bRes = TRUE;
281
282 EXIT:
283
284
285 if(bRes == FALSE)
286 {
287 g_vtBuf.clear();
288 }
289
290 CloseHandle(hDev);
291 LeaveCriticalSection(&g_csBuf);
292
293
294 return bRes;
295 }
最后这个例子,从另一个角度说明了,goto并不一定是鸡肋,在某些特定的环境下,只有它才能拯救代码于优雅之境地。
文章末尾,我们稍微总结一下。为了达到代码优雅的目的,我们首选__try;只有代码中用到了C++异常,导致和SEH冲突,我们才拿起饱受非议的goto,以完成我们优雅之目的。
如何写优雅的代码(2)——#define?const?还是enum?
//========================================================================
//TITLE:
// 如何写优雅的代码(2)——#define?const?还是enum?
//AUTHOR:
// norains
//DATE:
// Tuesday 21-July-2009
//Environment:
// WINCE5.0 + VS2005
//========================================================================
#define,const,enum:这三者有何关联?一个是宏定义,一个是静态修饰符,最后一个还是枚举类型。是不是有点像养麦皮打浆糊——粘不到一起?如果我们将范围缩小再缩小,让三者都只局限于“固定值”,那么千丝万缕的关系就了然于纸上——至少,有共同点了。
更多内容,请到EEWPRLD下载中心一览全貌!
本帖最后由 tiankai001 于 2014-10-9 10:44 编辑