您的位置:首页 > 编程语言

如何写优雅的代码(1)——灵活使用goto和__try

2009-07-16 12:26 555 查看
//========================================================================
//TITLE:
// 如何写优雅的代码(1)——灵活使用goto和__try
//AUTHOR:
// norains
//DATE:
// Thursday 16-July-2009
//Environment:
// WINCE5.0 + VS2005
//========================================================================

goto是毒药?凡是能用goto的地方,肯定能用结构化方式来实现相同的目的!估计很多朋友都对这论断不会陌生,甚至可以说,太熟悉了!但能实现并不代表优雅。不信?我们接下来看看。

假设我们有一个函数,需要实现如下功能:将一个驱动某些内容读取到缓存区去;又因为该缓存是全局公用的,所以我们很自然采用互斥量来进行控制。首先,我们坚持采用结构化方式实现,很可能我们的代码类似如下:

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,看看会发生什么情形:

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出场咯:

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中,并且成功读取之后,我们还想输出每个数值,那么我们代码可以如下:

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<BYTE>::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:

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<BYTE>::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,以完成我们优雅之目的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: