C++异常

异常:是指在程序运行周期发生的异常事件,一般情况下是不可预测的。

C的错误处理方式

终止程序

如assert,缺陷:用户难以接受。如发生内存错误,除0错误时就会终止程序

返回错误码

缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口都是通过把错误码放到errno中,表示错误

setjmp和longjmp

结构化异常的基础,发生异常则跳转至对应的模块,不过一般情况下很少使用

C++异常概念

异常是一种处理错误的机制,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数直接或间接地调用这个错误

throw:当问题出现时,程序会通过关键字throw抛出一个异常
catch:在处理异常的位置,通过catch关键字捕获异常,捕获异常后可以对异常进行处理,这个处理的语句叫做异常处理器
try:try块中的代码标识将被激活的特定异常,其后通常跟一个或多个catch块

如果有一个块抛出一个异常,捕获异常的方法会使用try和catch关键字。try块中放置可能抛出异常的代码,ttry块中的代码被称为保护代码,具体语法如下:

	try{
		//保护的标识代码
	}
	catch (ExceptionName e1)
	{
		//异常处理器
	}
	catch (ExceptionName e2)
	{
		//异常处理器
	}
	catch (ExceptionName e3)
	{
		//异常处理器
	}

异常的使用

异常的抛出和获取

异常的抛出和匹配原则

1、异常是通过抛出对象而引发的,该对象的类型决定了应该激活了哪个catch的处理代码
2、被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
3、抛出异常对象后,会生成一个临时对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在catch之后被销毁
4、catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么
5、实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获

函数调用链中异常栈展开匹配原则

1、首先检查throw本身是否在try块内部,如果是,再查找匹配的catch语句。如果存在匹配的,则调到catch的地方进行处理
2、如果匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch
3、如果到达main函数的栈,依旧没有匹配的,则终止程序。
上述这个沿着调用链查找匹配的catch子句的过程称为栈展开
4、找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行

#include <iostream>
using namespace std;

double Division(int a, int b)
{
	if (b == 0)
	{
		throw "Disivion by zero condition!";
	}
	else
	{
		return ((double)a / (double)b);
	}
}
void Function()
{
	int length, time;
	cin >> length >> time;
	cout << Division(length, time) << endl;
}

int main()
{
	try
	{
		Function();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "Unknown Exception" << endl;
	}

	return 0;
}

栈

异常的重新抛出

单个的catch可能不能完全处理一个异常,在进行一些校正处理后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理

#include <iostream>
using namespace std;

double Division(int a, int b)
{
	if (b == 0)
	{
		throw "Disivion by zero condition!";
	}
	else
	{
		return ((double)a / (double)b);
	}
}
void Function()
{
	int* array = new int[10]; 
	try
	{
		int length, time;
		cin >> length >> time;
		cout << Division(length, time) << endl;
	}
	catch (...)
	{
		delete[] array;
		cout << "delete[]" << array << endl;
		throw;
	}
	
	// ...
	
	cout << "delete[]" << array << endl;
	delete[] array;
}

int main()
{
	try
	{
		Function();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "Unknown Exception" << endl;
	}

	return 0;
}

异常安全

构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏,句柄未关闭等)
C++中异常经常会导致资源泄漏的问题,如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++常使用RAII(智能指针)来解决以上问题

异常规范

1、异常规格说明的目的是为了让函数使用者知道该函数可能抛出哪些异常,可以在函数后面接throw(类型),列出这个函数可能抛掷的所有异常类型
2、函数的后面接throw(),表示函数不抛出异常
3、若无异常接口声明,则此函数可以抛掷任何类型的异常

异常总结

异常的优点:

1、异常对象定义好了 ,相比错误码的方式可以清晰准确地展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好地定位程序的bug
2、返回错误码的传统方式有个很大的问题:在函数调用链中,深层的函数返回了错误,那么我们需要逐层返回错误,最外层才能拿到错误
3、很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,使用它们也需要使用异常
4、很多测试框架都使用异常,这样能更好地使用单元测试等进行白盒测试
5、部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码处理方式。比如T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误

异常的缺点

1、异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛出异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难
2、异常会有一些性能的开销,当然在现代硬件速度很快的情况下,这个影响基本忽略不计
3、C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题,这个需要使用RAII来处理资源的管理问题
4、C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常混乱
5、异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获得用户会很难做,所以异常规范有两点:抛出异常类型都继承自一个基类;函数是否抛出异常、抛什么异常,都适用 func() throw()的方式规范化


THE END

发布了80 篇原创文章 · 获赞 16 · 访问量 6万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览