c++异常处理

什么是异常处理?
异常处理就是处理程序中的错误。
c异常处理方式(传统错误处理方式):
1:终止程序
2:返回一个表示错误的值,附加错误码(GetLastError())
3:返回一个合法值,让程序处于某种非法的状态(坑爹的atoi())
4 : 调用一个预先准备好在出现“错误”的情况下用的函数(回调函数)
5:暴力解决方式: abort() 或者exit()
6 :使用goto语句
7 :set jmp() 和long jmp()组合

c++异常处理
异常,当一个函数发现自己无法处理错误时抛出异常,让函数的调用者直接或者间接的处理这个问题。

异常的抛出和捕获
1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码。
2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
3. 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个
抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。
栈展开
抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。
首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。
如果有匹配的,则处理。没有则退出当前函数栈,继续在调用函数的栈中进行查找。
不断重复上述过程。若到达main函数的栈,依旧没有匹配的,则终止程序。
上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。
找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
异常捕获的匹配规则
异常对象的类型与catch说明符的类型必须完全匹配。只有以下几种情况例外:
1. 允许从非const对象到const的转换。
2. 允许从派生类型到基类类型的转换。
3. 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针。

class Exception
{
public :
Exception(int errId = 0, const char * errMsg = "" )
: _errId(errId )
, _errMsg(errMsg )
{}
void What () const
{
cout<<"errId:" <<_errId<< endl;
cout<<"errMsg:" <<_errMsg<< endl;
}
private :
int _errId ; // 错误码
string _errMsg ; // 错误消息
};
void Func1 ()
{
throw string ("Throw Func1 string");
}
void Func2 ()
{
try
{
Func1();
}
catch(string & errMsg)
{
cout<<errMsg <<endl;
//Exception e (1, "Rethorw Exception");
//throw e ;
// throw;
// throw errMsg;
}
}
void Func3 ()
{
try
{
Func2();
}
catch (Exception & e)
{
e.What ();
}
}

异常规范
在函数声明之后,列出该函数可能抛出异常类型,并保证该函数不会抛出其他类型的异常。
1、成员函数在类内声明和类外定义两处必须有相同的异常规范。
2、函数抛出一个没有被列在它异常规范中的异常时(且函数中抛出异常没有在函数内部进行处理),
系统调用C++标准库中定义的函数unexpected().
3、如果异常规范为throw(),则表示不得抛出任何异常,该函数不用放在try块中。
4、派生类的虚函数的异常规范必须与基类虚函数的异常规范一样或更严格(是基类虚函数的异常
的子集)。因为:派生类的虚函数被指向基类类型的指针调用时,保证不会违背基类成员函数的异常
规范。
异常与构造函数&析构函数
1. 构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不
完整或没有完全初始化。
2. 析构函数主要完成资源的清理,需要保证不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)

class Exception1 : public exception
{
public :
Exception1(int errId = 0, const char * errMsg = "" )
: _errId(errId )
, _errMsg(errMsg )
{}
virtual const char* what() const
{
cout<<"errId:" <<_errId<< endl;
cout<<"errMsg:" <<_errMsg<< endl;
return _errMsg .c_str();
}
private :
int _errId ; // 错误码
string _errMsg ; // 错误消息
};
class Exception2 : public exception
{
public :
Exception2(int errId = 0, const char * errMsg = "" )
: _errId(errId )
, _errMsg(errMsg )
{}
virtual const char* what() const
{
cout<<"errId:" <<_errId<< endl;
cout<<"errMsg:" <<_errMsg<< endl;
return _errMsg .c_str();
}
private :
int _errId ; // 错误码
string _errMsg ; // 错误消息
};
void Func ()
{
try
{
throw Exception1 (1, "Exception1");
throw Exception1 (2, "Exception2");
}
catch(exception & e)
{
e.what ();
}
}
void Func ()
{
try{
int* p = new int[0x7fffffff/4];
}
catch(std::bad_alloc&ba)
{
std::cout << "bad_alloc caught: " << ba.what () << '\n'<< endl;
}catch(exception & e)
{
std::cout << "exception<-bad_alloc caught: " << e.what () << '\n';
}
}

编写自己的异常类
1. 为什么要编写自己的异常类?
① 标准库中的异常是有限的;
② 在自己的异常类中,可以添加自己的信息。(标准库中的异常类值允许设置一个用来描述异常的字符串)。
2. 如何编写自己的异常类?
① 建议自己的异常类要继承标准异常类。因为C++中可以抛出任何类型的异常,所以我们的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开发时。
② 当继承标准异常类时,应该重载父类的what函数和虚析构函数。
③ 因为栈展开的过程中,要复制异常类型,那么要根据你在类中添加的成员考虑是否提供自己的复制构造函数。
用类来封装资源分配和释放
为什么要使用类来封装资源分配和释放?
为了防止内存泄露。因为在函数中发生异常,那么对于动态分配的资源,就不会自动释放,必须要手动显式释放,否则就会内存泄露。而对于类对象,会自动调用其析构函数。如果我们在析构函数中显式delete这些资源,就能保证这些动态分配的资源会被释放。
如何编写这样的类?
将资源的分配和销毁用类封转起来。在析构函数中要显式的释放(delete或delete[])这些资源。这样,若用户代码中发生异常,当作用域结束时,会调用给该类的析构函数释放资源。这种技术被称为:资源分配即初始化。(resource allocation is initialization,缩写为”RAII”)。
节选自《The C++ Programming Language》 ——C++之父Bjarne Stroustrup
1. Don’t use exceptions where more local control structures will suffice; 当局部的控制能够处理时,不要使用异常;
2. Use the “resource allocation is initialization” technique to manage resources; 使用“资源分配即初始化”技术去管理资源;
3. Minimize the use of try-blocks. Use “resource acquisition is initialization” instead of explicit handler code; 尽量少用try-catch语句块,而是使用“资源分配即初始化”技术。
4. Throw an exception to indicate failure in a constructor; 如果构造函数内发生错误,通过抛出异常来指明。
5. Avoid throwing exceptions from destructors; 避免在析构函数中抛出异常。
6. Keep ordinary code and error-handling code separate; 保持普通程序代码和异常处理代码分开。
7. Beware of memory leaks caused by memory allocated by new not being released in case of an exception; 小心通过new分配的内存在发生异常时,可能造成内存泄露。
8. Assume that every exception that can be thrown by a function will be thrown; 如果一个函数可能抛出某种异常,那么我们调用它时,就要假定它一定会抛出该异常,即要进行处理。
9. Don’t assume that every exception is derived from class exception; 要记住,不是所有的异常都继承自exception类。
10. A library shouldn’t unilaterally terminate a program. Instead, throw an exception and let a caller decide; 编写的供别人调用的程序库,不应该结束程序,而应该通过抛出异常,让调用者决定如何处理(因为调用者必须要处理抛出的异常)。
11. Develop an error-handling strategy early in a design; 若开发一个项目,那么在设计阶段就要确定“错误处理的策略”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值