【C++进阶之路】异常篇

文章目录

  • 前言
  • 一、异常
    • 1.简单使用
    • 2.注意事项
    • 3.异常体系
      • ①C++标准异常体系
      • ②自定义异常体系
    • 4.总结
      • 优点
      • 缺点

前言

  是否知道C语言独特的错误处理方式——返回错误码,我们可以根据错误码来识别错误信息,比如识别了错误码,我们再用strerror函数把错误码对应的错误信息打印出来,于是便可知道哪错了。那C++是如何处理错误的呢?

一、异常

补充:C语言处理错误还可以用assert,不过在release版本下是不会弹出错误警告的,只会显示退出代码异常,因此assert是强调应该在调试期间,就把错误扼杀在摇篮中。

1.简单使用

  • 先来感受一下异常的使用方式
double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}
void Func()
{
	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl;
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "unkown exception" << endl;
	}

	return 0;
}

调试一下:

【C++进阶之路】异常篇_第1张图片

  • 最终运行结果:
    【C++进阶之路】异常篇_第2张图片

再来分析一下具体语法:

  • try 大括号里面是可能存在异常的语句或者函数。
  • catch 用于捕获可能存在的异常。
  • throw抛出异常信息,通过catch进行捕获。

  • 细节1:throw会抛给最近的栈帧的catch。
double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}
void Func()
{
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;//
	}
	catch (...)
	{
		cout << "unkown exception" << endl;
	}

}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "unkown exception" << endl;
	}

	return 0;
}

这里发生异常就会抛给调用Division的函数Func,因为是最近的一层异常。

画张图总结一下:

【C++进阶之路】异常篇_第3张图片

  • 细节2:catch的语法逻辑是跟if else if else 的逻辑一样的,是从最上面的catch,依次往下进行执行,如果存在合适的参数就进去,如果不存在就继续往下执行,直到碰到catch(…)——捕获任意类型的异常为止。如果还没有,就看下一层栈帧里面的catch的参数是否有所匹配,如果有就进去,或者再遇到catch(…)为止,如果到最后一层栈帧还没有,那就报错!

  • 细节3:catch(…)——也就是最后一道防线,如果你要再进行throw,一定要判断下一层栈帧中是否也有catch(…)或者是否有参数匹配的。否则还是会报错的。

  • 细节4:捕获异常之后,如果没有再次抛出的动作,那还按照当前栈帧之后的代码进行运行。

try
{
	//要检查的可能会抛异常的语句或者函数。
	//...
}
catch (const exception& e)
{
	cout << e.what() << endl;
}
catch (...)
{
	throw;//这里就是将捕获的异常进行再次抛出。
	cout << "未知异常" << endl;
}
  • 细节5:一次只能throw一次异常。
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Division by zero condition!";
		throw 1;//是不会走这一句代码的!
	}		

总结:

  1. 可以抛任意类型的错误,但一定要能够捕获,不然是会报错的。
  2. 抛出异常是会给最近一层的栈帧进行处理的。
  3. catch的运行逻辑,类似与if else if else ,且走的是匹配的参数。
  4. catch(…)可以接收任意类型的参数,也可以抛给下一层有try catch的栈帧查看是否合适的参数,如果没有抛给下一层,那按照当前栈帧继续往下运行代码。

2.注意事项

  1. 在完成对象的初始化工作时,不要轻易抛出异常,否则可能会导致,对象的初始化工作,没有做完,可能之后使用的是一个不完整的对象。
  2. 在完成对象的资源清理工作时,也不要随意抛出异常,否则可能会导致内存泄漏等问题。

看这样一段代码:

int add(int n1, int n2)
{
	return n1 + n2;
}
void func()
{
	int* n1 = new int(0);
	//当到这里出现异常时,只需抛出错误信息即可。
	int* n2 = new int(0);
	//当到这里出现异常时,我们不仅需要抛出错误信息,还得返回一个n1的指针进行资源的释放。
	cout << add(*n1, *n2) << endl;
	//当这里出现异常时,需要抛出错误信息,还得需要返回两个指针进行释放。
}
int main()
{
	try
	{
		func();
	}
	catch (...)
	{

	}
	return 0;
}
  • 这是一件异常导致的很麻烦的事情,问题就先留到这里,等在智能指针时一并解决。

3.异常体系

①C++标准异常体系

【C++进阶之路】异常篇_第4张图片

  • 说明:箭头的指向是父类。

【C++进阶之路】异常篇_第5张图片

  • 注:蓝色方框的是常用的异常类型。

总结:

  1. 采用了继承的方式来达到异常的复用。
  2. 这样设计再加上多态可避免屎山代码。
  3. 返回的信息更加的全面而有针对性(通过类进行返回)。

举例:

int main()
{
	try
	{
		//可能存在异常的代码
	}
	catch (const exception& e)
	//这里实现了多态:通过基类的指针和引用
	//来指向子类的对象。
	{
		cout << e.what() << endl;
	}
	//后面无需再列一串的代码进行匹配,只需要最后一道防线即可。
	catch (...)
	{
		cout << "unknown exception" << endl;
	}
	return 0;
}
  • 异常规范
// 这里表示这个函数会抛出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;
  • 缺点1:不是强制的,可以不使用,因为兼容C语言,因此无法强制。
  • 缺点2:C++98的throw可能会过于繁琐,如果抛出的异常类型过多,写起来很烦。

②自定义异常体系

  • 在实际应用当中我们也是根据C++标准异常体系来用的,不过库里的显然不符合所有场景的需求,因此我们需要根据实际的需求来自己写一个异常类(符合实际需求的)但都得继承一下exception,方便统一进行捕获。

4.总结

优点

  1. 相比较C语言的返回值和断言的错误,异常能跳过多层栈帧,并且返回的错误信息更加的准确。
  2. 一些常见的库里面也存在异常,因此具有兼容性和移植性。
  3. 模板函数的异常通过返回值是无法处理的,抛异常能更好地解决这类问题。

缺点

  1. 因为可能会跳过多层栈帧,因此可能会打乱执行流,也就是说发生错误了,按照正常逻辑顺序,打的断点可能会直接跳过。
  2. 异常通常是以对象形式进行返回的,可能开销有点大,不过也只是有点而已,CPU很快,影响可以说忽略不计了。
  3. 异常的执行流乱跳,可能会导致内存泄漏的问题,具体样例看上面的代码。解决方法RAII,智能指针部分我们统一解决。
  4. C++的标准异常体系定义的太简陋了,因此会导致各个公司都有一套自己的标准体系。
  5. 尽量按照规范进行抛异常,因为一般异常都是在一个固定的区域进行捕获的,乱抛异常可能会导致无法定位异常的准确位置的问题。不过这个东西,C++并没有强制,不过规范是为了更为安全的使用和书写代码,因此还是按照规范来吧。

你可能感兴趣的:(C++进阶之路,c++,java,开发语言)