C++异常(1) - 异常介绍

引言

异常,让一个函数可以在发现自己无法处理的错误时抛出一个异常,希望它的调用者可以直接或者间接处理这个问题。而传统错误处理技术,检查到一个局部无法处理的问题时:

1.终止程序(例如atol,atoi,输入NULL,会产生段错误,导致程序异常退出,如果没有core文件,找问题的人一定会发疯)

2.返回一个表示错误的值(很多系统函数都是这样,例如malloc,内存不足,分配失败,返回NULL指针)

3.返回一个合法值,让程序处于某种非法的状态(最坑爹的东西,有些第三方库真会这样)

4.调用一个预先准备好在出现"错误"的情况下用的函数。

第一种情况是不允许的,无条件终止程序的库无法运用到不能当机的程序里。第二种情况,比较常用,但是有时不合适,例如返回错误码是int,每个调用都要检查错误值,极不方便,也容易让程序规模加倍(但是要精确控制逻辑,我觉得这种方式不错)。第三种情况,很容易误导调用者,万一调用者没有去检查全局变量errno或者通过其他方式检查错误,那是一个灾难,而且这种方式在并发的情况下不能很好工作。至于第四种情况,本人觉得比较少用,而且回调的代码不该多出现。

使用异常,就把错误和处理分开来,由库函数抛出异常,由调用者捕获这个异常,调用者就可以知道程序函数库调用出现错误了,并去处理,而是否终止程序就把握在调用者手里了。

但是,错误的处理依然是一件很困难的事情,C++的异常机制为程序员提供了一种处理错误的方式,使程序员可以更自然的方式处理错误。

首先看一个例子:

#include<stdlib.h>  
#include<crtdbg.h>  
#include <iostream>  
#include <stdexcept>
using namespace std;

// 内存泄露检测机制  
#define _CRTDBG_MAP_ALLOC   
#ifdef _DEBUG  
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)  
#endif  

// 自定义异常类  
class MyExcepction  
{  
public:  

	// 构造函数,参数为错误代码  
	MyExcepction(int errorId)  
	{  
		// 输出构造函数被调用信息  
		std::cout << "MyExcepction is called" << std::endl;  
		m_errorId = errorId;  
	}  

	// 拷贝构造函数  
	MyExcepction( MyExcepction& myExp)  
	{  
		// 输出拷贝构造函数被调用信息  
		std::cout << "copy construct is called" << std::endl;  
		this->m_errorId = myExp.m_errorId;  
	}  

	~MyExcepction()  
	{  
		// 输出析构函数被调用信息  
		std::cout << "~MyExcepction is called" << std::endl;  
	}  

	// 获取错误码  
	int getErrorId()  
	{  
		return m_errorId;  
	}  

private:      
	// 错误码  
	int m_errorId;  
};  

int main(int argc, char* argv[])  
{  
	// 内存泄露检测机制  
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );  

	// 可以改变错误码,以便抛出不同的异常进行测试  
	int throwErrorCode = 110;  

	std::cout << " input test code :" << std::endl;  
	std::cin >> throwErrorCode;  
	
	//try{
	//	if ( throwErrorCode == 911) {
	//	/*(进入catch前对象已经被析构) , throw 类似于return
	//	*这里写法不对, 当我们抛出一个异常, 我们就应该有相应的catch来捕获
	//	*而try和catch是成对出现的,
	//	*/
	//	//MyExcepction glob_myStru(911);  
	//	//throw    &glob_myStru;			
	//	
	//	/* 我们来测试抛出一个runtime_error异常, 但没有相应的catch处理情况*/
	//	throw    runtime_error("balabala");			

	//	} 
	//}
	//catch(range_error err){
	//	std::cout << "执行了 catch(range_error err) " << std::endl;  
	//}
	///* 下面这个catch 所有的异常都会进入 , 类似于switch中的default,对于上面
	//	的异常抛出, 若是没有下面的处理, 则运行时候会提示有未处理的异常 */
	//catch(...){
	//	std::cout << "执行了 catch(...) " << std::endl;  

	//}
	

	try 
	{  
		if ( throwErrorCode == 110)  
		{  
			MyExcepction myStru(110);  

			// 抛出对象的地址 -> 由catch( MyExcepction*    pMyExcepction) 捕获  
			// 这里该对象的地址抛出给catch语句,不会调用对象的拷贝构造函数  

			//try块中的myStru在try块结束时被析构了(进入catch前对象已经被析构),
			//虽然在catch中仍然能够访问其getErrorId函数
			throw    &myStru;      
		}  
		else if ( throwErrorCode == 119 )  
		{  
			MyExcepction myStru(119);  

			// 抛出对象,这里会通过拷贝构造函数创建一个临时的对象传出给catch  
			// 由catch( MyExcepction    myExcepction) 捕获  
			// 在catch语句中会再次调用通过拷贝构造函数创建临时对象复制这里传过去的对象  
			// throw结束后myStru会被析构掉  
			throw    myStru;      
		}  
		else if ( throwErrorCode == 120 )  
		{  
			// 不提倡这样的抛出方法  
			// 这样做的话,如果catch( MyExcepction*    pMyExcepction)中不执行delete操作则会发生内存泄露  

			// 由catch( MyExcepction*    pMyExcepction) 捕获  
			MyExcepction * pMyStru = new MyExcepction(120);   
			throw pMyStru;      
		}  
		else 
		{  
			// 直接创建新对象抛出  
			// 相当于创建了临时的对象传递给了catch语句  
			// 由catch接收时通过拷贝构造函数再次创建临时对象接收传递过去的对象  
			// throw结束后两次创建的临时对象会被析构掉  
			throw MyExcepction(throwErrorCode);      
		}      
	}  
	catch( MyExcepction*    pMyExcepction)  
	{  
		// 输出本语句被执行信息  
		std::cout << "执行了 catch( MyExcepction*    pMyExcepction) " << std::endl;  

		// 输出错误信息  
		std::cout << "error Code : " << pMyExcepction->getErrorId()<< std::endl;  

		// 异常抛出的新对象并非创建在函数栈上,而是创建在专用的异常栈上,不需要进行delete  
		//delete pMyExcepction;  
	}  
	//catch ( MyExcepction myExcepction)  
	//{  
	//	// 输出本语句被执行信息  
	//	std::cout << "执行了 catch ( MyExcepction myExcepction) " << std::endl;  

	//	// 输出错误信息  
	//	std::cout << "error Code : " << myExcepction.getErrorId()<< std::endl;  
	//}  
	/* 
	*1.如果用了引用, 就不能用上面的非引用
	*2.当有多个catch时,如果第1个是引用,就能改变异常对象,第2个catch到的就是被修改过的异常对象了。
	*如果第1个不是引用,就不能改变异常对象,第2个catch到的就和第1个一样了
	*3.还有就是,非引用采用值传递,且不说效率,类能不能成功复制这是首先要关注的
	*/
	catch ( MyExcepction &myExcepction)  
	{  
		// 输出本语句被执行信息  
		std::cout << "执行了 catch ( MyExcepction &myExcepction) " << std::endl;  

		// 输出错误信息  
		std::cout << "error Code : " << myExcepction.getErrorId()<< std::endl;  
	}  
	catch(...)  // 三个点则表示捕获所有类型的异常  
	{  
		// 输出本语句被执行信息  
		std::cout << "执行了 catch(...) " << std::endl;  

		// 处理不了,重新抛出给上级  
		throw ;  
	}  

	// 暂停  
	system("pause");

	return 0;  
}

二、c++异常处理机制

C++异常处理机制是一个用来有效地处理运行错误的非常强大且灵活的工具,它提供了更多的弹性、安全性和稳固性,克服了传统方法所带来的问题.
    
异常的抛出和处理主要使用了以下三个关键字: try、 throw 、 catch 。
   
抛出异常即检测是否产生异常,在C++中,其采用throw语句来实现,如果检测到产生异常,则抛出异常。该语句的格式为:
    

    throw 表达式;
    

如果在try语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被try语句块后的某个catch语句所捕获并处理,捕获和处理的条件是被抛弃的异常的类型与catch语句的异常类型相匹配。由于C++使用数据类型来区分不同的异常,因此在判断异常时,throw语句中的表达式的值就没有实际意义,而表达式的类型就特别重要。
 
try-catch语句形式如下 :

try 
{  
        包含可能抛出异常的语句;  
}  
catch(类型名 [形参名]) // 捕获特定类型的异常  
{  
 
}  
catch(类型名 [形参名]) // 捕获特定类型的异常  
{  
 
}  
catch(...)    // 三个点则表示捕获所有类型的异常  
{  
}

三、异常的接口声明

为了加强程序的可读性,使函数的用户能够方便地知道所使用的函数会抛出哪些异常,可以在函数的声明中列出这个函数可能抛出的所有异常类型,例如:

   

    void fun() throw( A,B,C,D);


这表明函数fun()可能并且只可能抛出类型(A,B,C,D)及其子类型的异常。

如果在函数的声明中没有包括异常的接口声明,则此函数可以抛出任何类型的异常,例如:

    

    void fun();
 

一个不会抛出任何类型异常的函数可以进行如下形式的声明:
 

    void fun() thow();

      
四、异常处理中需要注意的问题

1. 如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止

2. 一般在异常抛出后资源可以正常被释放,但注意如果在类的构造函数中抛出异常,系统是不会调用它的析构函数的,处理方法是:如果在构造函数中要抛出异常,则在抛出前要记得删除申请的资源。

3. 异常处理仅仅通过类型而不是通过值来匹配的,所以catch块的参数可以没有参数名称,只需要参数类型。

4. 函数原型中的异常说明要与实现中的异常说明一致,否则容易引起异常冲突。
 
5. 应该在throw语句后写上异常对象时,throw先通过Copy构造函数构造一个新对象,再把该新对象传递给 catch. 

那么当异常抛出后新对象如何释放?

异常处理机制保证:异常抛出的新对象并非创建在函数栈上,而是创建在专用的异常栈上,因此它才可以跨接多个函数而传递到上层,否则在栈清空的过程中就会被销毁。所有从try到throw语句之间构造起来的对象的析构函数将被自动调用。但如果一直上溯到main函数后还没有找到匹配的catch块,那么系统调用terminate()终止整个程序,这种情况下不能保证所有局部对象会被正确地销毁。
   
6. catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常扑获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获。
   
7. 编写异常说明时,要确保派生类成员函数的异常说明和基类成员函数的异常说明一致,即派生类改写的虚函数的异常说明至少要和对应的基类虚函数的异常说明相同,甚至更加严格,更特殊。

http://www.cnblogs.com/ggjucheng/archive/2011/12/18/2292089.html#top

本文出自 “ Jhuster的专栏 ” 博客  http://ticktick.blog.51cto.com/823160/191881
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值