1.异常和异常处理
异常指的是程序运行时的反常行为,这些行为超出函数正常的功能范围。典型的异常包括编译错误、失去数据库的连接以及运行错误等。异常使得我们能将问题的检测和解决过程分离开来。
异常处理的基本思想:执行一个函数的过程中发现异常,可以不用在本函数内立即进行处理, 而是抛出该异常,让函数的调用者直接或间接处理这个问题。
C++异常处理机制由3个模块组成:try(检查)、throw(抛出)、catch(捕获)
2.栈展开
有两个函数F1(),F2(),F2()调用F1(),mian()调用F2(),并在F1()抛出一个异常,在main()用catch语句捕获。栈展开过程如下
栈展开过程沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的catch子句为止;或者也可能一直没找到匹配的catch,则退出主函数终止查找过程。如果找到了就执行catch子句的代码,当执行完这个catch子句之后,找到try块关联的最后一个catch子句之后的点继续执行。
3.捕获异常
catch子句中的异常声明看起来像一个形参的函数形参列表,如果catch无须访问抛出的表达式,就可以忽略捕获形参的名字。
3.1异常捕获的匹配规则:
try 块里面抛出那种类型的异常,则catch里面捕获哪种类型的异常。
一般情况下,类型必须完全匹配,但以下3种情况可以进行类型转换:
a:允许从非常量到常量的转换。 即throw 1个非const对象,catch 1个const对象
b:允许从派生类类型到基类类型的转换。 即throw 1个派生类对象,catch 1个基类对象,相当于发生了切片行为
c:将数组转换为指向数组类型的指针,函数转换为指向函数类型的指针。(与函数传参有点类似)
3.2重新抛出
当一个单独的catch语句不能完整的处理某个异常,在执行了某些校正操作以后,当前的catch可能会决定由调用链更上一层的函数接着来处理异常。一条catch语句通过重新抛出的操作异常传递给另一个catch语句。这里的重新抛出仍然是一条throw语句,只不过不包含任何表达式:
throw;//将当前的异常处理对象沿着调用链向上传递
3.3捕获所有异常的处理代码
为了一次性捕获所有的异常,我们使用省略号作为异常声明这样的处理代码称为捕获所有异常的处理代码,形如catch(...)。一条获所有异常的语句可以与任意类型的异常匹配。
3.4捕获异常注意事项:
(1)一个异常没有被捕获,则它将终止当前程序;
(2) 构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化;
(3)析构函数主要完成资源的清理,需要保证不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等);
(4)抛出指针要求在任和对应的处理代码存放的地方,指针所指的对象都必须存在;
(5)catch(...)必须放在catch语句的最后的位置,因为出现在捕获所有异常语句后面的catch语句将永远不会被匹配。
4.标准exception类层次
4.1标准异常类的继承体系以及对应解释:
4.2模拟实现exception的部分功能
类型exception仅仅定义了拷贝构造函数、拷贝复制运算符、一个虚析构函数(保证对象释放时总是调用"对象自己的函数")和一个名为what的纯虚函数(父类的函数根本没有必要或者无法实现,完全要依赖子类去实现的话,可以把此函数设为virtual 函数名=0 我们把这样的函数(方法)称为纯虚函数。子类必须重写了才能使用)。
#include <iostream>
#include <string>
using namespace std;
class Exception
{
public:
Exception(int ErrId,const char* ErrMsg)
:_ErrId(ErrId)
,_ErrMsg(ErrMsg)
{}
virtual const char* What() const = 0;
protected:
int _ErrId; //错误码
string _ErrMsg; //错误消息
};
class BadAlloc:public Exception
{
public:
BadAlloc(const char* msg = "")
:Exception(1, msg)
{
_ErrMsg = _ErrMsg +"alloc memory failed";
}
virtual const char* What() const
{
return _ErrMsg.c_str();
}
};
class RangError : public Exception
{
public:
RangError(const char* msg = "")
:Exception(2, msg)
{
_ErrMsg = _ErrMsg +" Rang error";
}
virtual const char* What() const
{
return _ErrMsg.c_str();
}
};
struct AA
{
public:
char ch[0x5fffffff];
};
template<class T>
T* New()
{
T* p = (T*)malloc(sizeof(T));
if (p == 0)
{
throw BadAlloc("New failed: ");
}
return new(p)T;
}
void F()
{
int a[10];
const int n = 20;
if (n > 9)
{
throw RangError("Access failure:");
}
else
{
cout << a[n] << endl;
}
}
int main()
{
try
{
AA* p1 = New<AA>();
}
catch (const Exception& e)
{
cout<<" "<<e.What()<<endl;
}
try
{
F();
}
catch (const Exception& e)
{
cout << " " << e.What() << endl;
}
return 0;
}
运行结果:
5.异常和返回错误码两者的优缺点
异常优点:
1. 可以强制对该错误进行处理,如果不处理则程序coredump
2. 可以从内层嵌套中直接跳出
3. 清晰知道发生错误的原因
4. 会进行堆栈解退
5. 很多第三方库在使用异常,比如boost库等
6. 很多测试框架在使用异常,比如gtest等
缺点:
1. 增加一定的开销
2. 破坏的程序的结构性。
3. 要正确使用异常机制不太容易,有很多需要小心的地方,一般需要配合RAII使用