C++学习-------程序异常处理

本文详细介绍了C语言中传统的错误处理方式,包括终止程序、返回错误码以及setjmp和longjmp组合。接着转向C++的异常处理机制,阐述了异常的概念、抛出和捕获原则,以及异常安全和异常规范。强调了自定义异常体系和标准库异常体系的重要性,并讨论了C++异常的优缺点,如清晰的错误定位、减少函数调用链的返回时间消耗,但也存在程序混乱和性能消耗的缺点。最后,提出了异常处理是必要的,对管理和操作有积极影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C语言中传统处理错误的方式

1.C语言中传统的处理错误的方式为三种:

  • 终止程序,如assert。缺点:用户难以承受,如发生内存错误,除0错误外,直接终止程序。
  • 返回错误码。这个很麻烦,人们要根据错误码去查找原因,并且错误码在系统的很多库的接口函数中被放到errno中,表示错误。
  • C标准中的setjmp和ongjmp组合。

总结:c语言中其实大部分错误的处理方式都是返回错误码,但还是有直接终止程序的,但是这样的危险比较大。

C++异常处理概念以及用法

异常处理概念

1.异常是一种错误的处理方式,当一个程序出现自己无法解决的错误的时候就可以抛出一个异常信号,让函数的直接或者间接调用者去处理。

  • throw:当一段程序出现异常的时候,就会抛出一个异常。而抛出的关键词就是throw。
  • try:try块中的程序有可能激活异常,就是说,如果你认为这段程序有可能会出现异常,那么你就空间让这段程序被包裹在try中即可。(并且try中的代码被称为保护代码)
  • catch:就是你想去处理问题的地方,通过异常处理程序捕获异常就用的是catch,并且catch一般情况下在try的后面使用,并且可以设计多个,异常可以通过合适的类型去选择适合自己的异常处理情况。

异常处理用法

1.异常的抛出和捕获:
①:异常的抛出原则:

  • 异常是通过抛出对象引发的,该对象的类型决定了应该使用那个catch去处理。
  • 被选中的处理代码,是距离异常抛出位置最近,也是与对象类型适合的哪一个。
  • 被抛出异常后,可能会生成一个异常对象的拷贝,因为抛出异常的对象很有可能是一个临时的对象,所以会生成一个对象的拷贝,并且这个拷贝的临时对象会在被catch以后进行销毁。
  • 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()
{
   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;
}

如同上面的代码,当函数发生异常的时候,会由Division进行抛出,并且会在
Func中去寻找捕获,如果没有会继续在main函数中去寻找,所以最好会被catch(…)进行捕获。

  1. 首先先检查throw是否在try中,如果是再寻找catch去捕获,如果寻找到,则调用到匹配的catch去捕获。
  2. 没有被当前捕获,那么就继续按照函数栈去下一个函数去寻找。
  3. 如果达到main函数,还是没有匹配的,那么就终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
  4. 找到并调用catch捕获后,会按照catch后的函数继续向下运行。

2.异常的重新抛出。
原因:由于有时候一个catch捕获的异常不能完全去处理这个异常,有时候需要重新在catch中再去抛出这个异常,让更上一层的函数去处理这个异常。
如下面代码:

int Division(int a, int b)
{
	if (b == 0)//当b为0时,就会抛出异常
	{
		throw "Division by zero condition!!!";
	}
	return a / b;
}
void Func()
{
	int l, r;
	cin >> l >> r;
	cout << Division(l, r) << endl;//抛出的异常在这个函数中没有捕获,就会继续向上抛出
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* error)//最终在此处异常被捕获,并且被处理。
	{
		cout << error << endl;
	}
	catch (...)
	{
		cout << "dont know error...." << endl;
	}
}

3.异常安全:
以下是要注意的几个点:

  • 最好不要在类的构造函数中去写出异常情况的处理,如果在构造函数中出现异常并且进行了异常抛出,那么就极有可能导致对象的构造不完全,或者没有完全初始化。
  • 也最好不要在类的析构函数中出现异常抛出,因为析构函数主要是回收资源,清理资源,如果由异常的抛出,很有可能造成资源泄漏。(内存泄漏,句柄未关闭等)。
  • C++异常中经常会出现资源泄漏的问题,比如在new和delete中出现了异常,导致内存的泄漏,在lock和unlock中出现异常而导致死锁,而对于这些,在C++11中都采用了RAII思想去应对。

4.异常规范:

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

例如以下代码:

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

C++自定义异常体系

1.自定义异常体系是非常实用的,因为对于一个大项目,如果有很多人去干这个项目,每个人都会有自己的抛出异常的一种方式,所以这个项目对于异常的抛出会有很多不同,所以必须得有一个属于自己的异常体系,这样不仅让这个异常抛出更加让人易懂,也更加的规格化。所以,一般都定义一个相同的基类去捕获派生类的异常,这样大家抛出的都是继承的派生类的对象,只需要一个基类去捕获即可。
2.如下图:
在这里插入图片描述

class MyException
{
protected:
	string _errmsg;
	int _id;
	//.......还可以设置别的
};
class MyExceptiontype1 : public MyException
{};
class MyExceptiontype2 : public MyException
{};
class MyExceptiontype3 : public MyException
{};
int main()
{
	try
	{
		// 抛出对象都是派生类对象
	}
	catch (const MyException& e) // 这里捕获父类对象就可以
	{}
	catch (...)
	{
		cout << "Unkown Exception" << endl;
	}
	return 0;
}

C++标准库异常体系

1.C++提供了一系列标准的异常,我们可以在程序中去使用这些标准的异常,并且它是有层次结构的。
如下图:
在这里插入图片描述
在这里插入图片描述
实际上我们可以去继承Execption来实现自己的异常类,但是一般情况下,都会有自己设置的异常处理。(既来之,则安之)

C++异常的优缺点

1.优点:

  • 异常对象定义好了,相比退出码,会让人清楚的了解到问题所在,甚至可以看到堆栈调用信息,可以直接锁定bug的位置。
  • 返回错误码有很大的问题,就是在函数调用链中,深层函数发现了错误,那么我们就得层层返回错误,最外层才能拿到错误。(就是在主函数中调用的函数去调用了一个错误的函数,那么这个错误函数出错了就会要层层返回到主函数中,这样才能获取错误码;而如果是抛出异常就不一样了,他就可以直接抛出来,直接被主函数的catch去捕捉,所以就减少了这么多函数返回的时间消耗)。
  • 很多第三方库都有异常。
  • 很多测试框架也都有异常,这样才能更好的进行一些测试。
  • 部分函数使用异常会更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T&operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

2.缺点:

  • 异常会导致程序乱跳,很混乱,以至于我们调试的时候会很难去分析程序。
  • 异常会出现一些性能的消耗。
  • C++没有垃圾回收机制,所有开辟的资源,到最后都需要去释放或者回收,但是有些异常会导致死锁等内存泄漏的问题,所以我们需要用RAII思想去处理,但是这样学习成本较高。
  • C++标准库异常体系做的不是很好,这样导致大家都想着自己定义属于自己的异常类,这样就会造成混乱。
  • 异常规范有两点:①:抛出异常的类型都源自于一个基类;②:函数是否抛出异常,抛出什么异常都是用func()函数和throw()规范化。

总结:异常是必要的,虽然是有缺点,但是在对我们对于管理和操作上都是非常有利的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值