冰冰学习笔记:异常处理

欢迎各位大佬光临本文章!!!

 

还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正。

本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬、帅哥、美女点点支持,您的每一分关心都是我坚持的动力。

我的博客地址:bingbing~bang的博客_CSDN博客https://blog.csdn.net/bingbing_bang?type=blog

我的gitee:冰冰棒 (bingbingsupercool) - Gitee.comhttps://gitee.com/bingbingsurercool


系列文章推荐

冰冰学习笔记:《位图与布隆过滤器》

冰冰学习笔记:《C++11的新特性》


目录

系列文章推荐

前言

1.C语言的传统处理与异常

2.异常的使用

2.1异常的抛出和捕获

2.2异常的重新抛出

2.3异常的安全和异常规范 

3.标准库提供的异常类

4.异常的优缺点


前言

        异常处理是C++对于代码运行错误的一种处理方式,与C语言的处理方式不同,异常处理并没有C语言的果断与强制,异常的处理显得更加柔和,只会影响部分代码无法运行,对于整体的逻辑并没有太大的影响。

1.C语言的传统处理与异常

        C语言传统的错误处理机制是抛出错误码或者程序崩溃,对于某些函数调用出错,函数会返回特定的返回值,并且设置错误码。这种处理方式的排错会比较复杂,对于调用层次很深的函数出错,错误码会层层返回,直到最外层才能进行处理。程序崩溃更是非常严重的影响,例如某一块程序发生内存错误,assert断言会直接触发导致程序崩溃,整个进程都会退出,照成很大的后果。

冰冰学习笔记:异常处理_第1张图片

        C++认为此种错误的处理方式太过于激进,程序某个模块的错误就有可能导致整体出现崩溃,对于长期运行的服务器来说,这是无法接受的情况。因此C++设置了异常处理机制,异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。

异常的实现需要使用三个关键字:

throw:当问题出现时,程序会抛出一个异常,通过关键字throw完成。

catch:在你想要处理问题的地方,通过异常处理程序捕获异常,可以包含多个catch语句。

try:try块中的代码将会运行,为可能出现异常的代码。在出现异常时将被后面的catch捕获。

因此异常的基本方式如下所示:

double Div(int x, int y)
{
	if (y == 0)
		throw"Division by zero condition!";//抛出字符串类型的错误
	else
		return x / y;
}
int main()
{
	try
	{
		double ret=Div(10, 0);
	}
	catch(const char* message)//捕捉字符串类型
	{
		cout << message << endl;
	}
	catch (...)//捕捉任意类型的错误
	{
		cout << "unknow exception" << endl;
	}
	return 0;
}

2.异常的使用

2.1异常的抛出和捕获

异常的使用和捕获需要遵循一些原则。

(1)异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。

(2)被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。

(3)抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象, 所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)。

(4)catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。

(5)实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象, 使用基类捕获。

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

(1)首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理

(2)没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。

(3)如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的 catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(...)捕获任意类型的异 常,否则当有异常没捕获,程序就会直接终止。

(4)找到匹配的catch语句并处理以后,会继续沿着catch子句后面继续执行。

int Func2(int x)
{
	if (x < 0)
		throw "x<0";
	if (x == 0)
		throw x;
	return x;
}
int Func1(int x,int y)
{
	try
	{
		if (x == 0)
		{
			throw x;
		}
		if (y == 0)
		{
			throw "y==0";
		}
		Func2(x - y);
		return x + y;
	}
	catch (const char* ch)
	{
		cout << ch << endl;
	}
}
int main()
{
	try {
		Func1(2,2);
	}
	catch(int x)
	{
		cout << "x的值: "<

        例如上面的一段测试代码如果Func1函数的参数为0,1。那么Func1函数将抛出整型值x,catch语句将会匹配int x,最终打印出x的值。这就是最佳匹配原则,而Func2函数的参数为-1,会抛出"x<0"的字符串,由于Func1函数中具备该类型的捕获,那么就会直接在Func1函数中进行异常的处理。 如果Func1函数的参数为2,2,那么Func2函数的参数将会变为0,此时函数将会抛出整形值x,但是Func2没有catch语句进行错误的捕获,将返回给调用层Func1函数,但是Func1函数的异常捕获并没有整形类型的捕获,此时异常将继续向上一层返回,直到main函数进行异常的捕获,如果main函数也没有进行异常的捕获,那么程序将会崩溃。

2.2异常的重新抛出

        当异常被catch语句捕获后,有可能该catch并不能完全处理一个异常,在进行一些处理后,希望交给外层的调用链函数来处理,catch语句块中可以将异常重新抛出。

冰冰学习笔记:异常处理_第2张图片

2.3异常的安全和异常规范 

        构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不 完整或没有完全初始化。析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏。

        C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄 漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题。

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

// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;

        在实际使用中异常规范并没有很多的作用,因为并非是强制使用,所以很少会有人遵守该规范。并且对于该规范来说,有时我们对某些函数添加了说明,表示该函数不会抛出异常,但是在实际使用中有可能会抛出一些不知名的异常,这就导致程序错误。

3.标准库提供的异常类

        实际上在日常的应用中,往往都会定义自己的异常体系来方便管理,因为如果一个项目中大家的异常随意抛出,外层的调用者基本无法进行捕获,但是我们定义一个异常类,抛出者抛出子类,外层使用基类进行接收即可。

实际上C++库中为我们提供了标准的异常类:

冰冰学习笔记:异常处理_第3张图片

 冰冰学习笔记:异常处理_第4张图片

 我们可以直接继承该类型的类创建自己的异常类,但是公司一般喜欢自己创建异常体系。

4.异常的优缺点

 异常的优点:

(1)异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包 含堆栈调用的信息,这样可以帮助更好的定位程序的bug。

(2)返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那 么我们得层层返回错误,最外层才能拿到错误,具体看下面的详细解释。

(3)很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们 也需要使用异常。

(4)部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如 T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回 值表示错误。

异常的缺点:

(1)异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会 导致我们跟踪调试时以及分析程序时,比较困难。

(2)异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。

(3) C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常 安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。

(4)C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。

(5).异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常 规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都 使用 func() throw();的方式规范化。

你可能感兴趣的:(C++笔记,学习)