c++---异常

  • C语言传统的处理错误的方式
  • C++异常概念
  • 异常的用法
  • 自定义异常体系
  • 标准库异常体系
  • 异常的优缺点
  1. C语言传统的处理错误的方式

传统的错误处理机制

  • 终止程序,比如assert,虽然能够及时的终止程序,但是用户难以接受,比如除0的时候我们只需要提示用户输入错误,不需要终止程序
  • 返回错误码,但是错误码不能够及时的让程序员明白是什么错误,需要自己去查询什么错误。
  • C语言标准库中的setjmp和longjmp组合。
  1. C++异常的概念
    异常时一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或者间接的调用者处理这个函数
  • throw:当问题出现的时候,程序会抛出一个异常,者都是使用throw关键字来完成的
  • catch:再您想要处理问题的地方,通常异常处理程序捕获异常,catch关键字用于捕获异常,可以又多个catch进行捕获。
  • try:try块中的代码标识将被激活的特定异常,它后面通常跟着一个或者多个catch块。
    如果有一个抛出一个异常,捕获异常的方法会使用try和catch关键字,try块中放置可能抛出异常的代码,try块中的代码被称为保护代码,使用try/catch语句的语法如下:
try{
//保护的代码
}catch( ExceptionName e1){
//catch块
}catch(ExceptionName e2){
//catch块
}
  1. 异常的使用
    3.1 异常的抛出和捕获
    异常的抛出原则

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

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

  • 首先检查throw本身是否在try块内部,如果时再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理
  • 没有没有匹配到catch则退出当前函数栈,继续再调用函数的栈中进行查找匹配的catch
  • 如果到达main函数的栈,依旧没有匹配的,则终止程序
  • 找到匹配的catch子句并处理之后,会继续沿着catch子句后面继续执行
    c++---异常_第1张图片

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

double Division(int a, int b) {
    // 当b == 0时抛出异常
     if (b == 0)    {
             throw "Division by zero condition!";
     }
      return (double)a / (double)b;
}
void Func() {    
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。    
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再    
	// 重新抛出去。    
	int* array = new int[10];
	try {        
	    int len, time;        
	    cin >> len >> time;        
	    cout << Division(len, time) << endl;    
	    } 
	 catch (...) {
	     cout << "delete []" << array << endl;        			
	     delete[] array;
	     throw;
	}
 	 cout << "delete []" << array << endl;
 	 delete[] array;
}
int main(){
	try{
		Func();
	}
	catch(const char* errmsg){
		cout<< errmsg << endl;
	}
	return 0;
}

3.3 异常安全

  • 构造函数完成对象的构造和初始化,最好不要再构造函数中抛出异常,否则可能导致对象不完整或者没有完全初始化
  • 析构函数主要完成资源的清理,最好不要再析构函数内抛出异常,否则可能导致资源泄漏。
  • C++中异常经常会导致资源泄漏的问题,比如再new和delete中抛出异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决上面的问题。

3.4 异常的 规范

  • 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有那些,可以在函数的后面接throw,类出这个函数可能抛出的所有异常类型
  • 函数的垢面接throw,表示函数不跑异常
  • 若没有异常接口声明,则函数可以抛出任何类型的异常。
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常 
void fun() throw(ABCD); 
// 这里表示这个函数只会抛出bad_alloc的异常 
void* operator new (std::size_t size) throw (std::bad_alloc); 
// 这里表示这个函数不会抛出异常 
void* operator new (std::size_t size, void* ptr) throw();
  1. 自定义异常体系

在实际中都会自己定义自己的异常体系,以免在写代码的时候随意抛出异常而不知道异常的类型。所以这时候使用继承的规范体系。这样在熬出的时候抛出派生类就可以了,在捕获的时候只需要捕获一个基类就可以了。
c++---异常_第2张图片

  1. C++标准库的异常体系
    C++标准库的异常体系就是使用父子类层次结构组织起来的。
    c++---异常_第3张图片
    异常的说明
    c++---异常_第4张图片

  2. 异常的优缺点
    优点

  • 异常对象定义好了,相对错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug
  • 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误。
  • 很多第三方库都包含异常,比如gtest,gmock等等常用的库,那么我们使用他们也需要使用异常
  • 很多测试框架都使用异常,这样更好的使用单元测试等进行白盒的测试
  • 部分函数使用异常更好的处理,比如构造函数没有返回值,不方便使用错误码方式处理。

缺点

  • 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛出异常就会乱跳,这回导致我们跟踪调试时以及分析程序时比较困难。
  • 异常会有一些性能的开销。
  • C++没有垃圾回收机制,资源需要自己管理,有了异常非常导致资源的泄漏,死锁等等
  • C++标准库中的异常体系定义不好,导致实际中斗志自己定义自己的
  • 异常使用的时候要规范,不然在外层捕获的时候会导致不知道捕获什么类型的异常。

你可能感兴趣的:(c++总结)