C++异常

C++异常

  • C语言传统的处理错误的方式
  • C++异常概念
  • 异常的使用
    • 异常的抛出和捕获
    • 异常安全
    • 异常规范
  • 自定义异常体系
  • C++标准库的异常体系
  • 异常的优缺点

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

  1. 终止程序:比如发生除0错误,空指针解引用等等;
  2. 返回错误码: 然后用户根据错误码区错误表中查早对应的错误;
    实际上C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。

C++异常概念

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。
throw: 当异常出现的时候,我们可以利用throw关键字来抛出一个任意类型的对象;
try: try语块中放入可能出现异常的代码片或函数,try 块中的代码被称为保护代码,通常与catch关键字配合使用;
catch: 用于捕获throw关键字抛出的异常,主要catch有参数且参数类型要与throw关键字抛出的对象类型一致才能成功捕捉throw抛出的异常;
具体用法如下:
C++异常_第1张图片

异常的使用

异常的抛出和捕获

异常的抛出和匹配原则:

  1. 一般我们都在调用链的最外层捕获异常,当然也可以在调用链中间以及抛出异常的地方进行捕获;
    eg:
    a.在抛出异常的地方,直接就捕捉异常,现抛现捕:
    C++异常_第2张图片
    b. 在调用链中间进行捕获异常:
    C++异常_第3张图片
    c.在最外层捕获异常(一般作法)
    C++异常_第4张图片
  2. throw关键字可以抛出任意类型的对象,但是编译器会利用该抛出的对象重新构建一个临时对象,然后再抛出,因此实际上throw抛出的是一个临时对象,因为编译器如果不做这一层保护的话,那么抛出的对象很有可能是一个局部对象,局部对象出了作用域就会被销毁,那么在catch的地方如果是利用引用捕获的话就会出现非法访问的错误,这里类似于函数的值返回的形式!
  3. throw抛出异常过后会去激活在调用链中距离最近的并且catch的参数类型与之匹配的catch段:
    C++异常_第5张图片
    当然,如果距离抛出异常最近的1一层catch没有合适的catch代码段,那么编译器会继续沿着调用链继续向前寻找合适的,直至抵达main函数:
    C++异常_第6张图片
    当来到main函数这一层都没有合适的catch语句来捕捉异常,那么程序会直接崩溃掉!C++异常_第7张图片
  4. catch(…)可以捕获任意类型的的异常;在某些时候,我们并不能确定一个函数或代码片到底会抛出那些异常,为了避免抛出未知异常而导致程序崩溃,我们一般利用catch(…)捕获任意类型的异常;
    C++异常_第8张图片
  5. 在实际中抛出和捕获类型有个例外,并不都是类型完全匹配,编译器允许我们抛出子类对象,然后利用父类进行捕捉;(实际中也经常使用)
    主要思想:我们可以写一个父类的异常类,该类具有异常的一些基本属性,如果当我们想要抛出具体的某个异常时,我们可以继承父类,然后再在子类中增加一些具体的属性,以便于我们能更加清除的描述这个异常,这样的话只要我们遵循这个游戏规则,那么无论我们抛出多少个不同的异常类对象,在最外层都只需要利用父类捕捉一次即可,避免了不同的抛出对象需要不同的catch语句来捕捉,当然如果我们想要查看某个具体的抛出对象的信息,我们可以在父类中写一个虚函数,然后子类重写这个虚函数,在catch语句中我们只需要调用这个虚函数就能构成多态,从而展现出对应抛出对象的具体错误:
    具体如下:
    C++异常_第9张图片
    C++异常_第10张图片
    当然,如果catch语句块中既有父类捕捉也有本类类型捕捉,两个都能正确捕捉,那么编译器会采用最近的一个catch代码块,

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

  1. throw抛出异常过后,会先检查抛出异常的地方是否在try
    语句块中,如果不在则销毁当前栈帧,跳到前一个栈帧上去继续检查;如果在,则会去catch语句块匹配是否有合适的类型,如果没有则则销毁当前栈帧,跳到前一个栈帧上去继续检查;如果有,则激活对应的catch代码片;
    2、如果到达main函数栈帧都没有合适的catch来激活那么,程序会被终止掉;上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异
    常,否则当有异常没捕获,程序就会直接终止。
    3、编译器找到合适的catch语句后,会沿着catch代码片继续向后执行!

C++异常_第11张图片

异常安全

  1. 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不
    完整或没有完全初始化;
  2. 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内
    存泄漏、句柄未关闭等)
    3.C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄
    漏,在lock和unlock之间抛出了异常导致死锁;
    针对以上两个问题,我们主要有两种方案来解决:
    1、采用RAII风格的智能指针或锁来解决;
    2、采用重新抛出的方式来进行解决:
    这里我们主要讲解重新抛出的方式来讲解:
    C++异常_第12张图片
    如果Division抛出异常,那么执行流会直接跳到catch的地方向下继续执行,这就会导致我们的array资源泄漏,这是我们不愿意看到的!那么如何保证我们既要在外部处理异常,又要防止内存泄漏呢?
    当然是重新抛出异常,我们先在Func里面捕获一下Division抛出的异常,然后捕获到这个异常过后不做处理异常,只释放资源,然后再重新抛出这个异常,外部就能重新捕获了:
    C++异常_第13张图片
    当然上面的作法还不保险,如果Division不抛出const char*类型的异常呢?
    抛出其它1异常不是都会造成内存泄漏吗,为此我们需要捕获Divsion抛出的所有异常,然后再重新抛出:
    C++异常_第14张图片
    但这终究是治标不治本,还得靠RAII的方式来完美解决这个问题;

异常规范

  1. 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的
    后面接throw(类型),列出这个函数可能抛掷的所有异常类型,当然列出来的不一定准确,有可能这个程序也会抛出声明的异常之外的异常,并不是一定只会抛出所声明的异常,因为可能当时开发者也可能没想到会抛出这个异常;
  2. 函数的后面接throw(),表示函数不抛异常。当然这也只是声明,只起到提醒的作用实际还是有可能抛出异常;
  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常。
// 这里表示这个函数会抛出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++98的风格;
C++觉得太麻烦了,于是就利用了一个关键字noexcept来解决,如果一个函数明确表示不会抛出异常,那么可以用noexcept关键字来标识

// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;

注意声明和实现的时候都需要将noexcept带上,不然会出错:
C++异常_第15张图片
正确写法:
C++异常_第16张图片
当然,如果你非不遵守规则明明都用noexcept关键字表示了该函数不会抛出异常,你非要抛个异常出了,你编译器是会报错的!

自定义异常体系

实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家
随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。
这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。
C++异常_第17张图片

C++标准库的异常体系

C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父
子类层次结构组织起来的,如下所示:
C++异常_第18张图片
C++异常_第19张图片

说明:实际中我们可以可以去继承exception类实现自己的异常类。但是实际中很多公司像上面一
样自己定义一套异常继承体系。因为C++标准库设计的不够好用

异常的优缺点

优点:
1、能够返回准确清晰的错误信息,可以帮助我们快速的定位错误,从而修改bug;
2、部分函数不是否返回错误码,比如构造、析构函数,这时候抛出异常就能完美的解决这个问题,准确清晰的返回错误信息;
缺点:
1、异常会导致执行流乱跳,并且非常混乱,如果是在申请资源和释放资源之间抛出异常,那么就会导致内存泄漏;如果是在申请锁和释放锁之间抛出异常,那么就会造成死锁;
2、C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。

总结: 异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外OO的语言基本都是
用异常处理错误,这也可以看出这是大势所趋。

你可能感兴趣的:(C++,c++)