使用C++异常出来错误的原因
C语言处理错误的方式有终止进程(assert),返回错误码,C标准库中setjmp和longjmp组合,但都有各自的缺陷:assert发生内存错误,除0错误时就会终止程序,错误码需要程序员查询对应的错误,总之,C语言中基本都是返回错误码的方式,部分采用错误码的方式。
C++中异常处理含throw,当问题出现,会抛出异常;catch,异常捕获,在你想处理问题时,通过异常处理程序捕捉异常;try,try中代码标示将被激活的特定异常,他后面通常跟着一个或多个catch块。
异常的使用
- 异常的抛出和使用
1.异常抛出与使用原则
异常是通过抛出对象而引发的,该对象的类型决定了应激活哪个catch的处理代码;
处理代码是与该类型匹配且离抛出异常距离最近的那一个;
抛出异常对象是时,会生成异常的临时对象,这个临时对象会被catch以后销毁;
catch可以捕捉任意类型的异常,但不知异常错误是什么;
异常中有个例外为可以抛出派生类对象,使用基类捕获。 - 在函数调用链中异常栈中展开匹配原则
首先检查throw是否在try块内部,若在查找catch语句。如果有匹配,调到catch处处理;
没有匹配到catch则退出函数栈,继续在调用函数的栈中查找匹配catch;
到达main函数还没有找到,则终止程序。
异常的重新抛出
有可能单个catch不能处理异常,在进行一些矫正处理后,希望再交给更外层的调用链函数来处理,catch则可以重新抛出异常交给更上层的函数进行处理。
异常安全
构造函数完成函数的构造和初始化,最好不要抛出异常,以免对不能完全初始化;
析构函数完成资源的清理,最好不要抛出异常,易造成内存泄漏;
异常规范
为了让函数使用者知道该函数可能抛出的异常是什么;
函数后面跟throw(),表示不抛出异常;
若无异常接口声明,则此函数可以抛出任何类型的异常。
自定义异常体系
一个项目中若随意抛出异常,外层调用者就没有办法使用,所以实际中都会自定义异常的规范体系,抛出继承的派生类对象,捕捉一个基类。
class Exception
{
protected:
int age;
string name;
};
class S : public Exception
{
};
class X : public Exception
{
};
int main()
{
try{
//抛出对象为派生类对象
}
catch(const Exception& e)
{
//捕捉基类对象
}
catch(...)
{
cout << "Unkown Exception" << endl;
}
return 0;
}
C++标准库中异常体系
资料地址:https://en.cppreference.com/w/cpp/error/exception
标准库中异常不够好用,一般都是自定义异常体系。
异常的优缺点
优点
- 更清晰的表示错误信息;
- 返回错误码的方式有一个问题,在函数调用中,深层的函数返回错误,之后需要层层返回错误,最外层才能够拿到错误;
- 很多的第三方库都包含异常,如boost、gtest、gmock等常用库;
- 很多测试框架都使用异常,更好的使用单元测试等进行白盒测试;
- 部分函数使用异常更好处理。
缺点
- 会导致程序的执行乱跳,跟踪调试以及分析程序比较困难;
- 会有性能开销;
- 没有垃圾回收机制,容易造成内存泄漏;
- 随意抛出异常,外层捕捉用户不易捕捉。