如何写优雅的代码(1)——灵活使用goto和__try
2009-09-28 10:12
477 查看
//========================================================================
//TITLE:
// 如何写优雅的代码(1)——灵活使用goto和__try
//AUTHOR:
// norains
//DATE:
// Thursday 16-July-2009
//Environment:
// WINCE5.0 + VS2005
//========================================================================
goto是毒药?凡是能用goto的地方,肯定能用结构化方式来实现相同的目的!估计很多朋友都对这论断不会陌生,甚至可以说,太熟悉了!但能实现并不代表优雅。不信?我们接下来看看。
假设我们有一个函数,需要实现如下功能:将一个驱动某些内容读取到缓存区去;又因为该缓存是全局公用的,所以我们很自然采用互斥量来进行控制。首先,我们坚持采用结构化方式实现,很可能我们的代码类似如下:
view plaincopy to clipboardprint?
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. }
BOOL ReadDeviceBuf() { EnterCriticalSection(&g_csBuf); //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); if(hDev == INVALID_HANDLE_VALUE) { LeaveCriticalSection(&g_csBuf); return FALSE; } //获取驱动设备的缓存大小 DWORD dwSize = 0; if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return FALSE; } //分配缓存 g_pBuf = new BYTE[dwSize]; if(g_pBuf == NULL) { CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return FALSE; } //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE) { delete []g_pBuf; g_pBuf = FALSE; CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return FALSE; } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return TRUE; }
没错,采用这种结构化的方式的确是解决了问题。可是,我们是不是有点别扭呢?每次出错,返回FALSE之前,都必须要清理一次资源。小函数也许还不是什么大问题,只要睁大眼睛,小心翼翼,还是能在后续的返回中正确处理资源释放的。但如果函数因为要加入某些功能越来越大,又或许是别人来维护这段代码,那能保证在返回前释放资源么?
接下来我们使用被大家鄙弃的goto,看看会发生什么情形:
view plaincopy to clipboardprint?
1. BOOL ReadDeviceBuf()
2. {
3. BOOL bRes = FALSE;
4.
5. EnterCriticalSection(&g_csBuf);
6.
7. DWORD dwSize = 0;
8.
9. //打开驱动设备
10. HANDLE hDev = CreateFile(TEXT("DEV1:"),
11. FILE_WRITE_ATTRIBUTES,
12. 0,
13. NULL,
14. OPEN_EXISTING,
15. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
16. NULL);
17.
18. if(hDev == INVALID_HANDLE_VALUE)
19. {
20. goto EXIT;
21. }
22.
23. //获取驱动设备的缓存大小
24. //DWORD dwSize = 0; //产生编译错误:initialization of 'dwSize' is skipped by 'goto EXIT'
25. if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
26. {
27. goto EXIT;
28. }
29.
30. //分配缓存
31. g_pBuf = new BYTE[dwSize];
32. if(g_pBuf == NULL)
33. {
34. goto EXIT;
35. }
36.
37. //从驱动中读取数据
38. if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)
39. {
40. goto EXIT;
41. }
42.
43. bRes = TRUE;
44.
45. EXIT:
46.
47. if(bRes == FALSE)
48. {
49. delete []g_pBuf;
50. g_pBuf = FALSE;
51. }
52.
53. CloseHandle(hDev);
54. LeaveCriticalSection(&g_csBuf);
55.
56. return bRes;
57. }
BOOL ReadDeviceBuf() { BOOL bRes = FALSE; EnterCriticalSection(&g_csBuf); DWORD dwSize = 0; //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); if(hDev == INVALID_HANDLE_VALUE) { goto EXIT; } //获取驱动设备的缓存大小 //DWORD dwSize = 0; //产生编译错误:initialization of 'dwSize' is skipped by 'goto EXIT' if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { goto EXIT; } //分配缓存 g_pBuf = new BYTE[dwSize]; if(g_pBuf == NULL) { goto EXIT; } //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE) { goto EXIT; } bRes = TRUE; EXIT: if(bRes == FALSE) { delete []g_pBuf; g_pBuf = FALSE; } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return bRes; }
怎么样?把所有的资源释放都放到EXIT段中,每个EnterCriticalSection都能对应一个LeaveCriticalSection,是不是显得比之前的更为优雅?还能说goto为鸡肋么?
不过,goto也不是尽善尽美,比如变量dwSize在goto之后就不能初始化,只能将局部变量的初始化放到第一个goto之前。按照C++的建议,变量的声明最好尽可能接近使用的地方。而放在第一个goto之前,摆明就是C作风嘛!
其实如果以本特例,直接声明dwSize而不进行初始化也是可行的;但这并不代表在别的情况下也能畅通无阻,也许有的程序就依赖于初始化的值,谁知道呢?
那有没有更为优雅的?可以解决这dwSize的位置问题的?答案自然是肯定的。不过,就必须请我们的__try出场咯:
view plaincopy to clipboardprint?
1. BOOL ReadDeviceBuf()
2. {
3. BOOL bRes = FALSE;
4.
5. EnterCriticalSection(&g_csBuf);
6.
7. //打开驱动设备
8. HANDLE hDev = CreateFile(TEXT("DEV1:"),
9. FILE_WRITE_ATTRIBUTES,
10. 0,
11. NULL,
12. OPEN_EXISTING,
13. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
14. NULL);
15.
16. __try
17. {
18. if(hDev == INVALID_HANDLE_VALUE)
19. {
20. __leave;
21. }
22.
23. //获取驱动设备的缓存大小
24. DWORD dwSize = 0;
25. if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
26. {
27. __leave;
28. }
29.
30. //分配缓存
31. g_pBuf = new BYTE[dwSize];
32. if(g_pBuf == NULL)
33. {
34. __leave;
35. }
36.
37. //从驱动中读取数据
38. if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)
39. {
40. __leave;
41. }
42.
43. bRes = TRUE;
44.
45. }
46. __finally
47. {
48.
49. if(bRes == FALSE)
50. {
51. delete []g_pBuf;
52. g_pBuf = FALSE;
53. }
54.
55. CloseHandle(hDev);
56. LeaveCriticalSection(&g_csBuf);
57. }
58.
59. return bRes;
60. }
BOOL ReadDeviceBuf() { BOOL bRes = FALSE; EnterCriticalSection(&g_csBuf); //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); __try { if(hDev == INVALID_HANDLE_VALUE) { __leave; } //获取驱动设备的缓存大小 DWORD dwSize = 0; if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { __leave; } //分配缓存 g_pBuf = new BYTE[dwSize]; if(g_pBuf == NULL) { __leave; } //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE) { __leave; } bRes = TRUE; } __finally { if(bRes == FALSE) { delete []g_pBuf; g_pBuf = FALSE; } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); } return bRes; }
哦耶!现在dwSize终于在它该出现的位置上了,是不是显得比goto更为优雅呢?
这段改写的代码采用的是SEH机制。因为SEH机制如果需要详细解释,就不是一言两语的事情,所以在此就略过,感兴趣的朋友可以自己在网上查找资料。在这里,只是说明一点,采用SEH机制,无论如何,最后基本上一定要运行__finally段代码,除非中间有中断。
最后一段是不是意味着凡是可以运用goto的地方都能采用__try替代?答案是否定的。特别是代码中采用了STL,SEH机制将会无能为力。
不信?我们添加一点代码段来看看事情的真相。假设我们不是通过数组来保留缓存,而是保留于STL的vector中,并且成功读取之后,我们还想输出每个数值,那么我们代码可以如下:
view plaincopy to clipboardprint?
1. BOOL ReadDeviceBuf()
2. {
3. BOOL bRes = FALSE;
4.
5. EnterCriticalSection(&g_csBuf);
6.
7. //打开驱动设备
8. HANDLE hDev = CreateFile(TEXT("DEV1:"),
9. FILE_WRITE_ATTRIBUTES,
10. 0,
11. NULL,
12. OPEN_EXISTING,
13. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
14. NULL);
15.
16. __try
17. {
18. if(hDev == INVALID_HANDLE_VALUE)
19. {
20. __leave;
21. }
22.
23. //获取驱动设备的缓存大小
24. DWORD dwSize = 0;
25. if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
26. {
27. __leave;
28. }
29.
30. //分配缓存
31. g_vtBuf.resize(dwSize);
32.
33. //从驱动中读取数据
34. if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)
35. {
36. __leave;
37. }
38.
39.
40. //打印每个数据
41. //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding”
42. for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)
43. {
44. printf("%d/n",*iter);
45. }
46.
47. bRes = TRUE;
48.
49. }
50. __finally
51. {
52.
53. if(bRes == FALSE)
54. {
55. g_vtBuf.clear();
56. }
57.
58. CloseHandle(hDev);
59. LeaveCriticalSection(&g_csBuf);
60. }
61.
62. return bRes;
63. }
BOOL ReadDeviceBuf() { BOOL bRes = FALSE; EnterCriticalSection(&g_csBuf); //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); __try { if(hDev == INVALID_HANDLE_VALUE) { __leave; } //获取驱动设备的缓存大小 DWORD dwSize = 0; if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { __leave; } //分配缓存 g_vtBuf.resize(dwSize); //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE) { __leave; } //打印每个数据 //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding” for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++) { printf("%d/n",*iter); } bRes = TRUE; } __finally { if(bRes == FALSE) { g_vtBuf.clear(); } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); } return bRes; }
很遗憾,这段代码无法编译通过。因为STL的迭代器中用到了对象,而对象会释放C++异常,而这和SEH有冲突。当然,我们完全可以用new来替代,以避开这个问题,但这样一来,却是使代码更峥嵘,离优雅更是八辈子打不到一个杆子上。
这时候,还是只能用goto:
view plaincopy to clipboardprint?
1. BOOL ReadDeviceBuf()
2. {
3. BOOL bRes = FALSE;
4.
5. EnterCriticalSection(&g_csBuf);
6.
7. //打开驱动设备
8. HANDLE hDev = CreateFile(TEXT("DEV1:"),
9. FILE_WRITE_ATTRIBUTES,
10. 0,
11. NULL,
12. OPEN_EXISTING,
13. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
14. NULL);
15.
16.
17. if(hDev == INVALID_HANDLE_VALUE)
18. {
19. goto EXIT;
20. }
21.
22. //获取驱动设备的缓存大小
23. DWORD dwSize = 0;
24. if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
25. {
26. goto EXIT;
27. }
28.
29. //分配缓存
30. g_vtBuf.resize(dwSize);
31.
32. //从驱动中读取数据
33. if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)
34. {
35. goto EXIT;
36. }
37.
38.
39. //打印每个数据
40. //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding”
41. for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)
42. {
43. printf("%d/n",*iter);
44. }
45.
46. bRes = TRUE;
47.
48. EXIT:
49.
50.
51. if(bRes == FALSE)
52. {
53. g_vtBuf.clear();
54. }
55.
56. CloseHandle(hDev);
57. LeaveCriticalSection(&g_csBuf);
58.
59.
60. return bRes;
61. }
BOOL ReadDeviceBuf() { BOOL bRes = FALSE; EnterCriticalSection(&g_csBuf); //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); if(hDev == INVALID_HANDLE_VALUE) { goto EXIT; } //获取驱动设备的缓存大小 DWORD dwSize = 0; if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { goto EXIT; } //分配缓存 g_vtBuf.resize(dwSize); //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE) { goto EXIT; } //打印每个数据 //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding” for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++) { printf("%d/n",*iter); } bRes = TRUE; EXIT: if(bRes == FALSE) { g_vtBuf.clear(); } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return bRes; }
最后这个例子,从另一个角度说明了,goto并不一定是鸡肋,在某些特定的环境下,只有它才能拯救代码于优雅之境地。
文章末尾,我们稍微总结一下。为了达到代码优雅的目的,我们首选__try;只有代码中用到了C++异常,导致和SEH冲突,我们才拿起饱受非议的goto,以完成我们优雅之目的。
//TITLE:
// 如何写优雅的代码(1)——灵活使用goto和__try
//AUTHOR:
// norains
//DATE:
// Thursday 16-July-2009
//Environment:
// WINCE5.0 + VS2005
//========================================================================
goto是毒药?凡是能用goto的地方,肯定能用结构化方式来实现相同的目的!估计很多朋友都对这论断不会陌生,甚至可以说,太熟悉了!但能实现并不代表优雅。不信?我们接下来看看。
假设我们有一个函数,需要实现如下功能:将一个驱动某些内容读取到缓存区去;又因为该缓存是全局公用的,所以我们很自然采用互斥量来进行控制。首先,我们坚持采用结构化方式实现,很可能我们的代码类似如下:
view plaincopy to clipboardprint?
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. }
BOOL ReadDeviceBuf() { EnterCriticalSection(&g_csBuf); //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); if(hDev == INVALID_HANDLE_VALUE) { LeaveCriticalSection(&g_csBuf); return FALSE; } //获取驱动设备的缓存大小 DWORD dwSize = 0; if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return FALSE; } //分配缓存 g_pBuf = new BYTE[dwSize]; if(g_pBuf == NULL) { CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return FALSE; } //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE) { delete []g_pBuf; g_pBuf = FALSE; CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return FALSE; } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return TRUE; }
没错,采用这种结构化的方式的确是解决了问题。可是,我们是不是有点别扭呢?每次出错,返回FALSE之前,都必须要清理一次资源。小函数也许还不是什么大问题,只要睁大眼睛,小心翼翼,还是能在后续的返回中正确处理资源释放的。但如果函数因为要加入某些功能越来越大,又或许是别人来维护这段代码,那能保证在返回前释放资源么?
接下来我们使用被大家鄙弃的goto,看看会发生什么情形:
view plaincopy to clipboardprint?
1. BOOL ReadDeviceBuf()
2. {
3. BOOL bRes = FALSE;
4.
5. EnterCriticalSection(&g_csBuf);
6.
7. DWORD dwSize = 0;
8.
9. //打开驱动设备
10. HANDLE hDev = CreateFile(TEXT("DEV1:"),
11. FILE_WRITE_ATTRIBUTES,
12. 0,
13. NULL,
14. OPEN_EXISTING,
15. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
16. NULL);
17.
18. if(hDev == INVALID_HANDLE_VALUE)
19. {
20. goto EXIT;
21. }
22.
23. //获取驱动设备的缓存大小
24. //DWORD dwSize = 0; //产生编译错误:initialization of 'dwSize' is skipped by 'goto EXIT'
25. if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
26. {
27. goto EXIT;
28. }
29.
30. //分配缓存
31. g_pBuf = new BYTE[dwSize];
32. if(g_pBuf == NULL)
33. {
34. goto EXIT;
35. }
36.
37. //从驱动中读取数据
38. if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)
39. {
40. goto EXIT;
41. }
42.
43. bRes = TRUE;
44.
45. EXIT:
46.
47. if(bRes == FALSE)
48. {
49. delete []g_pBuf;
50. g_pBuf = FALSE;
51. }
52.
53. CloseHandle(hDev);
54. LeaveCriticalSection(&g_csBuf);
55.
56. return bRes;
57. }
BOOL ReadDeviceBuf() { BOOL bRes = FALSE; EnterCriticalSection(&g_csBuf); DWORD dwSize = 0; //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); if(hDev == INVALID_HANDLE_VALUE) { goto EXIT; } //获取驱动设备的缓存大小 //DWORD dwSize = 0; //产生编译错误:initialization of 'dwSize' is skipped by 'goto EXIT' if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { goto EXIT; } //分配缓存 g_pBuf = new BYTE[dwSize]; if(g_pBuf == NULL) { goto EXIT; } //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE) { goto EXIT; } bRes = TRUE; EXIT: if(bRes == FALSE) { delete []g_pBuf; g_pBuf = FALSE; } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return bRes; }
怎么样?把所有的资源释放都放到EXIT段中,每个EnterCriticalSection都能对应一个LeaveCriticalSection,是不是显得比之前的更为优雅?还能说goto为鸡肋么?
不过,goto也不是尽善尽美,比如变量dwSize在goto之后就不能初始化,只能将局部变量的初始化放到第一个goto之前。按照C++的建议,变量的声明最好尽可能接近使用的地方。而放在第一个goto之前,摆明就是C作风嘛!
其实如果以本特例,直接声明dwSize而不进行初始化也是可行的;但这并不代表在别的情况下也能畅通无阻,也许有的程序就依赖于初始化的值,谁知道呢?
那有没有更为优雅的?可以解决这dwSize的位置问题的?答案自然是肯定的。不过,就必须请我们的__try出场咯:
view plaincopy to clipboardprint?
1. BOOL ReadDeviceBuf()
2. {
3. BOOL bRes = FALSE;
4.
5. EnterCriticalSection(&g_csBuf);
6.
7. //打开驱动设备
8. HANDLE hDev = CreateFile(TEXT("DEV1:"),
9. FILE_WRITE_ATTRIBUTES,
10. 0,
11. NULL,
12. OPEN_EXISTING,
13. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
14. NULL);
15.
16. __try
17. {
18. if(hDev == INVALID_HANDLE_VALUE)
19. {
20. __leave;
21. }
22.
23. //获取驱动设备的缓存大小
24. DWORD dwSize = 0;
25. if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
26. {
27. __leave;
28. }
29.
30. //分配缓存
31. g_pBuf = new BYTE[dwSize];
32. if(g_pBuf == NULL)
33. {
34. __leave;
35. }
36.
37. //从驱动中读取数据
38. if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)
39. {
40. __leave;
41. }
42.
43. bRes = TRUE;
44.
45. }
46. __finally
47. {
48.
49. if(bRes == FALSE)
50. {
51. delete []g_pBuf;
52. g_pBuf = FALSE;
53. }
54.
55. CloseHandle(hDev);
56. LeaveCriticalSection(&g_csBuf);
57. }
58.
59. return bRes;
60. }
BOOL ReadDeviceBuf() { BOOL bRes = FALSE; EnterCriticalSection(&g_csBuf); //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); __try { if(hDev == INVALID_HANDLE_VALUE) { __leave; } //获取驱动设备的缓存大小 DWORD dwSize = 0; if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { __leave; } //分配缓存 g_pBuf = new BYTE[dwSize]; if(g_pBuf == NULL) { __leave; } //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE) { __leave; } bRes = TRUE; } __finally { if(bRes == FALSE) { delete []g_pBuf; g_pBuf = FALSE; } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); } return bRes; }
哦耶!现在dwSize终于在它该出现的位置上了,是不是显得比goto更为优雅呢?
这段改写的代码采用的是SEH机制。因为SEH机制如果需要详细解释,就不是一言两语的事情,所以在此就略过,感兴趣的朋友可以自己在网上查找资料。在这里,只是说明一点,采用SEH机制,无论如何,最后基本上一定要运行__finally段代码,除非中间有中断。
最后一段是不是意味着凡是可以运用goto的地方都能采用__try替代?答案是否定的。特别是代码中采用了STL,SEH机制将会无能为力。
不信?我们添加一点代码段来看看事情的真相。假设我们不是通过数组来保留缓存,而是保留于STL的vector中,并且成功读取之后,我们还想输出每个数值,那么我们代码可以如下:
view plaincopy to clipboardprint?
1. BOOL ReadDeviceBuf()
2. {
3. BOOL bRes = FALSE;
4.
5. EnterCriticalSection(&g_csBuf);
6.
7. //打开驱动设备
8. HANDLE hDev = CreateFile(TEXT("DEV1:"),
9. FILE_WRITE_ATTRIBUTES,
10. 0,
11. NULL,
12. OPEN_EXISTING,
13. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
14. NULL);
15.
16. __try
17. {
18. if(hDev == INVALID_HANDLE_VALUE)
19. {
20. __leave;
21. }
22.
23. //获取驱动设备的缓存大小
24. DWORD dwSize = 0;
25. if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
26. {
27. __leave;
28. }
29.
30. //分配缓存
31. g_vtBuf.resize(dwSize);
32.
33. //从驱动中读取数据
34. if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)
35. {
36. __leave;
37. }
38.
39.
40. //打印每个数据
41. //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding”
42. for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)
43. {
44. printf("%d/n",*iter);
45. }
46.
47. bRes = TRUE;
48.
49. }
50. __finally
51. {
52.
53. if(bRes == FALSE)
54. {
55. g_vtBuf.clear();
56. }
57.
58. CloseHandle(hDev);
59. LeaveCriticalSection(&g_csBuf);
60. }
61.
62. return bRes;
63. }
BOOL ReadDeviceBuf() { BOOL bRes = FALSE; EnterCriticalSection(&g_csBuf); //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); __try { if(hDev == INVALID_HANDLE_VALUE) { __leave; } //获取驱动设备的缓存大小 DWORD dwSize = 0; if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { __leave; } //分配缓存 g_vtBuf.resize(dwSize); //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE) { __leave; } //打印每个数据 //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding” for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++) { printf("%d/n",*iter); } bRes = TRUE; } __finally { if(bRes == FALSE) { g_vtBuf.clear(); } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); } return bRes; }
很遗憾,这段代码无法编译通过。因为STL的迭代器中用到了对象,而对象会释放C++异常,而这和SEH有冲突。当然,我们完全可以用new来替代,以避开这个问题,但这样一来,却是使代码更峥嵘,离优雅更是八辈子打不到一个杆子上。
这时候,还是只能用goto:
view plaincopy to clipboardprint?
1. BOOL ReadDeviceBuf()
2. {
3. BOOL bRes = FALSE;
4.
5. EnterCriticalSection(&g_csBuf);
6.
7. //打开驱动设备
8. HANDLE hDev = CreateFile(TEXT("DEV1:"),
9. FILE_WRITE_ATTRIBUTES,
10. 0,
11. NULL,
12. OPEN_EXISTING,
13. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
14. NULL);
15.
16.
17. if(hDev == INVALID_HANDLE_VALUE)
18. {
19. goto EXIT;
20. }
21.
22. //获取驱动设备的缓存大小
23. DWORD dwSize = 0;
24. if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)
25. {
26. goto EXIT;
27. }
28.
29. //分配缓存
30. g_vtBuf.resize(dwSize);
31.
32. //从驱动中读取数据
33. if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)
34. {
35. goto EXIT;
36. }
37.
38.
39. //打印每个数据
40. //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding”
41. for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)
42. {
43. printf("%d/n",*iter);
44. }
45.
46. bRes = TRUE;
47.
48. EXIT:
49.
50.
51. if(bRes == FALSE)
52. {
53. g_vtBuf.clear();
54. }
55.
56. CloseHandle(hDev);
57. LeaveCriticalSection(&g_csBuf);
58.
59.
60. return bRes;
61. }
BOOL ReadDeviceBuf() { BOOL bRes = FALSE; EnterCriticalSection(&g_csBuf); //打开驱动设备 HANDLE hDev = CreateFile(TEXT("DEV1:"), FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, NULL); if(hDev == INVALID_HANDLE_VALUE) { goto EXIT; } //获取驱动设备的缓存大小 DWORD dwSize = 0; if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE) { goto EXIT; } //分配缓存 g_vtBuf.resize(dwSize); //从驱动中读取数据 if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE) { goto EXIT; } //打印每个数据 //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding” for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++) { printf("%d/n",*iter); } bRes = TRUE; EXIT: if(bRes == FALSE) { g_vtBuf.clear(); } CloseHandle(hDev); LeaveCriticalSection(&g_csBuf); return bRes; }
最后这个例子,从另一个角度说明了,goto并不一定是鸡肋,在某些特定的环境下,只有它才能拯救代码于优雅之境地。
文章末尾,我们稍微总结一下。为了达到代码优雅的目的,我们首选__try;只有代码中用到了C++异常,导致和SEH冲突,我们才拿起饱受非议的goto,以完成我们优雅之目的。
相关文章推荐
- 如何写优雅的代码(1)——灵活使用goto和__try
- 如何写优雅的代码(1)——灵活使用goto和__try:评论反馈
- 如何写优雅的代码(1)——灵活使用goto和__try
- 如何写优雅的代码(1)——灵活使用goto和__try:评论反馈
- 如何写优雅的代码(1)——灵活使用goto和__try
- 如何写优雅的代码(1)——灵活使用goto和__try
- 如何写优雅的代码(1)——灵活使用goto和__try:评论反馈
- try与finally一起使用时,try语句发生异常,如何继续执行代码到finally语句块
- 如何优雅的写代码——使用RecyleView的基类Adapter
- windows下如何快速优雅的使用python的科学计算库?
- .net如何使用C++的代码?托管C ++和非托管C ++互相访问
- (JSP)在文本域中显示超链接——如何在Servlet中使用服务器端代码
- 使用思维导图,优雅的完成自己的代码
- android开发,如何使用git托管代码,如何在android studio上使用git管理代码
- 如何使用Eclipse和GDB对JNI代码进行调试(JAVA和C)
- 如何使用eclipse自动调整代码格式
- 如何优雅地使用Vim?(趣味篇)
- 如何使用Spring优雅地处理REST异常
- 如何使用Java代码将GBK编码格式的工程转换为UTF-8编码格式的工程
- 实测如何使用c#代码判断服务器是否安装iis