目录
一、异常的引入
二、C++异常的关键字
三、异常的抛出与处理规则
四、异常缺陷的处理
五、自定义异常体系
六、异常规范
七、异常安全
八、异常的优缺点
1.优点
2.缺点
一、异常的引入
传统的C语言处理异常的方式有两种:
1.终止程序:使用assert断言语句,如果发生内存错误等,比如内存泄漏或者除0错误,都会直接终止程序。
2.返回错误码:通过错误码判断发生的异常的类型是什么,如系统的很多库的接口程序通过把错误码放到errno中,表示错误。
在实际中的C语言程序基本都是通过返回错误码的方式来处理错误的,部分情况下使用终止程序来处理比较严重的错误。
二、C++异常的关键字
目前市面上的大部分的主流语言都是使用异常机制来处理错误的,当一个函数发现自己无法处理一些错误的时候会进行抛异常处理,让函数直接跳转到捕获异常的地方去处理异常。
下面介绍C++处理异常的几个关键字:
1.throw:当问题出现的时候,程序会抛出一个异常,这是通过throw关键字来完成的。
2.catch:通过异常处捕获异常,catch块主要用于处理异常,或者执行一些其他的操作。
3.try:try块中的代码标识将被激活的特定异常,它的后面一般会跟一个catch块。
三、异常的抛出与处理规则
double Exception(int n1, int n2)
{
if (n2 == 0)
{
throw "Devision by zero condition";
}
else
{
return ((double)n1 / (double)n2);
}
}
void func()
{
int n1, n2;
cin >> n1 >> n2;
cout << Exception(n1, n2) << endl;
}
int main()
{
try
{
func();
}
catch (const char* errmsg)
{
cout << "Devision by zero condition" << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
当向n2传入的值为0的时候,调用throw抛出异常,catch会捕获该异常进行处理。
- 1.异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪一个catch的处理代码。
-
2.被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的一个。
在这段程序中,有一个调用链:main()->Func()->Disvision(),在Disvision中抛出异常就会沿着调用链寻找能捕获异常的catch来执行。假设Func()中有一个与main()中一模一样的catch函数,那么除0的异常就会优先调用Func()中的catch,因为它离着最近。 -
3.catch的异常其实是抛出对象的拷贝,因为真正抛出的对象在出作用域的时候就已经被销毁了,拷贝的对象在catch成功之后也会自动销毁。
- 4.catch(…)可以捕获任意类型的异常,但问题是不知道异常错误是什么。
- 5.实际中抛出和捕获的匹配原则有一个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获。(要引入多态)
- 6.当catch异常之后,会沿着catch后的子句继续执行。
四、异常缺陷的处理
double Exception(int n1, int n2)
{
if (n2 == 0)
{
throw "Devision by zero condition";
}
else
{
return ((double)n1 / (double)n2);
}
}
void func()
{
int* array = new int[10];
int n1, n2;
cin >> n1 >> n2;
cout << Exception(n1, n2) << endl;
delete[] array;//一旦抛出异常,这里就不会被执行
}
int main()
{
try
{
func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
对于这段代码而言,一旦Exception抛出异常就会立刻执行主函数中的catch,delete释放内存就不会被执行。因此我们需要对这种情况进行处理,即在Func中也捕获一次异常,但是不对异常进行处理,只是释放空间:
void func()
{
int* array = new int[10];
int n1, n2;
cin >> n1 >> n2;
try
{
cout << Exception(n1, n2) << endl;
}
catch (...)
{
cout << "释放空间" << endl;
delete[] array;
throw;
}
}
在释放空间之后,直接调用throw,"..."是任何异常都接收,只有"throw"表示将捕获到的异常再跑出去。
五、自定义异常体系
六、异常规范
1.异常规格说明的目的是让函数使用者知道,可以在函数的后面接throw类(类型),列出这个函数可能抛出的所有异常。
2.函数后面接throw(),表示函数不抛异常。
3.若无异常接口声明,则此函数可以抛掷任何类型的异常。
void func() throw(A, B, C, D);//只会抛A/B/C/D中的某中类型的异常
void* operator new(size_t size)throw(bad alloc);//这里表示这个函数只会抛出bad_alloc这种异常
void* operator new(size_t size, void* ptr) throw();这个函数不会抛异常
在C++中,引入了noexception
bool exception(int x, int y) noexcept(noexcept(x > y))
{
return x > y;//表示如果x>y不发生异常,则Compare函数不会发生异常。
}
七、异常安全
构造函数完成对象的初始化,最好不要在构造函数中抛异常,否则可能导致对象不完整,或没有完全初始化。
析构函数主要完成资源的清理,最好不要在析构函数中抛异常,否则可能导致资源泄露。
C++异常经常会导致资源泄露的问题,比如在new和delete中抛异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAll来解决以上问题,关于RAll我们在智能指针中讲解。
八、异常的优缺点
1.优点
1.相比错误码,更加清晰展示出错信息。
2.很多库中包含异常,boost,gtest,gmock等常用的库,使用它们也需要异常。
3.部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理,比如T&operator[]这样的函数,只有一个返回值,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。
2.缺点
1.异常类似goto,会导致程序的执行流乱跳,并且非常混乱。
2.C++没有垃圾回收机制,可能导致内存泄漏。
3.各个公司的异常体系不同,有一定的学习成本。