到此为此,异常的匹配算是说清楚了,总结一下,异常匹配的时候基本上遵循下面几条规则:
  · 异常匹配除了必须要是严格的类型匹配外,还支持下面几个类型转换.
  · 允许非常量到常量的类型转换,也是说可以抛出一个非常量类型,然后使用catch捕捉对应的常量类型版本
  · 允许从派生类到基类的类型转换
  · 允许数组被转换为数组指针,允许函数被转换为函数指针
  假想一种情况,当我要实现一代代码的时候,希望无论抛出什么类型的异常我都可以捕捉到,目前来说我们只能写上一大堆的catch语句捕获所有可能在代码中出现的异常来解决这个问题,很显然这样处理起来太过繁琐,幸好C++提供了一种可以捕捉任何异常的机制,可以使用下列代码中的语法。
  catch(...) {
  //异常处理器,这里可以捕捉任何异常,带来的问题是无法或者异常信息
  }
  如果你要实现一个函数库,你捕捉了你的函数库中的一些异常,但是你只是记录日志,并不去处理这些异常,处理异常的事情会交给上层调用的代码来处理.对于这样的一个场景C++也提供了支持.
  try{
  throw Exception("I am a exception");
  }catch(...) {
  //log the exception
  throw;
  }
  通过在catch语句块中加入一个throw,可以把当前捕获到的异常重新抛出.在异常抛出的那一节中,我在代码中抛出了一个异常,但是我没有使用任何catch语句来捕获我抛出的这个异常,执行上面的程序会出现下面的结果.
  terminate called after throwing an instance of 'MyError'
  Aborted (core dumped)
  为什么会出现这样的结果呢?,当我们抛出一个异常的时候,异常会随着函数调用关系,一级一级向上抛出,直到被捕获才会停止,如果终没有被捕获将会导致调用terminate函数,上面的输出是自动调用terminate函数导致的,为了保证更大的灵活性,C++提供了set_terminate函数可以用来设置自己的terminate函数.设置完成后,抛出的异常如果没有被捕获会被自定义的terminate函数进行处理.下面是一个使用的例子:
#include <exception>
#include <iostream>
#include <cstdlib>
using namespace std;
class MyError {
const char* const data;
public:
MyError(const char* const msg = 0):data(msg)
{
//idle
}
};
void do_error() {
throw MyError("something bad happend");
}
//自定义的terminate函数,函数原型需要一致
void terminator()
{
cout << "I'll be back" << endl;
exit(0);
}
int main()
{
//设置自定义的terminate,返回的是原有的terminate函数指针
void (*old_terminate)() = set_terminate(terminator);
do_error();
}
  上面的代码会输出I'll be back
  到此为此关于异常匹配的我所知道的知识点都已经介绍完毕了,那么接着可以看看下一个话题,异常中的资源清理.
  异常中的资源清理
  在谈到局部跳转的时候,说到局部调转不会调用对象的析构函数,会导致内存泄露的问题,C++中的异常则不会有这个问题,C++中通过堆栈反解将已经定义的对象进行析构,但是有一个例外是构造函数中如果出现了异常,那么这会导致已经分配的资源无法回收,下面是一个构造函数抛出异常的例子:
#include <iostream>
#include <string>
using namespace std;
class base
{
public:
base()
{
cout << "I start to construct" << endl;
if (count == 3) //构造第四个的时候抛出异常
throw string("I am a error");
count++;
}
~base()
{
cout << "I will destruct " << endl;
}
private:
static int count;
};
int base::count = 0;
int main()
{
try{
base test[5];
} catch(...){
cout << "catch some error" << endl;
}
}
  上面的代码输出结果是:
  I start to construct
  I start to construct
  I start to construct
  I start to construct
  I will destruct
  I will destruct
  I will destruct
  catch some error
  在上面的代码中构造函数发生了异常,导致对应的析构函数没有执行,因此实际编程过程中应该避免在构造函数中抛出异常,如果没有办法避免,那么一定要在构造函数中对其进行捕获进行处理.后介绍一个知识点是函数try语句块,如果main函数可能会抛出异常该怎么捕获?,如果构造函数中的初始化列表可能会抛出异常该怎么捕获?下面的两个例子说明了函数try语句块的用法:
  #include <iostream>
  using namespace std;
  int main() try {
  throw "main";
  } catch(const char* msg) {
  cout << msg << endl;
  return 1;
  }
  main函数语句块,可以捕获main函数中抛出的异常.
class Base
{
public:
Base(int data,string str)try:m_int(data),m_string(str)//对初始化列表中可能会出现的异常也会进行捕捉
{
// some initialize opt
}catch(const char* msg) {
cout << "catch a exception" << msg << endl;
}
private:
int m_int;
string m_string;
};
int main()
{
Base base(1,"zhangyifei");
}
  上面说了很多都是关于异常的使用,如何定义自己的异常,编写异常是否应该遵循一定的标准,在哪里使用异常,异常是否安全等等一系列的问题,下面会一一讨论的.
  标准异常
  C++标准库给我们提供了一系列的标准异常,这些标准异常都是从exception类派生而来,主要分为两大派生类,一类是logic_error,另一类则是runtime_error这两个类在stdexcept头文件中,前者主要是描述程序中出现的逻辑错误,例如传递了无效的参数,后者指的是那些无法预料的事件所造成的错误,例如硬件故障或内存耗尽等,这两者都提供了一个参数类型为std::string的构造函数,这样可以将异常信息保存起来,然后通过what成员函数得到异常信息.
#include <stdexcept>
#include <iostream>
#include <string>
using namespace std;
class MyError:public runtime_error {
public:
MyError(const string& msg = "") : runtime_error(msg) {}
};
//runtime_error logic_error 两个都是继承自标准异常,带有string构造函数
//
int main()
{
try {
throw MyError("my message");
} catch(MyError& x) {
cout << x.what() << endl;
}
}
  异常规格说明
  假设一个项目中使用了一些第三方的库,那么第三方库中的一些函数可能会抛出异常,但是我们不清楚,那么C++提供了一个语法,将一个函数可能会抛出的异常列出来,这样我们在编写代码的时候参考函数的异常说明即可,但是C++11中这中异常规格说明的方案已经被取消了,所以我不打算过多介绍,通过一个例子看看其基本用法即可,重点看看C++11中提供的异常说明方案:
#include <exception>
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
class Up{};
class Fit{};
void g();
//异常规格说明,f函数只能抛出Up 和Fit类型的异常
void f(int i)throw(Up,Fit) {
switch(i) {
case 1: throw Up();
case 2: throw Fit();
}
g();
}
void g() {throw 47;}
void my_ternminate() {
cout << "I am a ternminate" << endl;
exit(0);
}
void my_unexpected() {
cout << "unexpected exception thrown" << endl;
// throw Up();
throw 8;
//如果在unexpected中继续抛出异常,抛出的是规格说明中的 则会被捕捉程序继续执行
//如果抛出的异常不在异常规格说明中分两种情况
//1.异常规格说明中有bad_exception ,那么会导致抛出一个bad_exception
//2.异常规格说明中没有bad_exception 那么会导致程序调用ternminate函数
// exit(0);
}
int main() {
set_terminate(my_ternminate);
set_unexpected(my_unexpected);
for(int i = 1;i <=3;i++)
{
//当抛出的异常,并不是异常规格说明中的异常时
//会导致终调用系统的unexpected函数,通过set_unexpected可以
//用来设置自己的unexpected汗函数
try {
f(i);
}catch(Up) {
cout << "Up caught" << endl;
}catch(Fit) {
cout << "Fit caught" << endl;
}catch(bad_exception) {
cout << "bad exception" << endl;
}
}
}
}
  上面的代码说明了异常规格说明的基本语法,以及unexpected函数的作用,以及如何自定义自己的unexpected函数,还讨论了在unexpected函数中继续抛出异常的情况下,该如何处理抛出的异常.C++11中取消了这种异常规格说明.引入了一个noexcept函数,用于表明这个函数是否会抛出异常
  void recoup(int) noexecpt(true);  //recoup不会抛出异常
  void recoup(int) noexecpt(false); //recoup可能会抛出异常
  此外还提供了noexecpt用来检测一个函数是否不抛出异常.
  异常安全
  异常安全我觉得是一个挺复杂的点,不光光需要实现函数的功能,还要保存函数不会在抛出异常的情况下,出现不一致的状态.这里举一个例子,大家在实现堆栈的时候经常看到书中的例子都是定义了一个top函数用来获得栈顶元素,还有一个返回值是void的pop函数仅仅只是把栈顶元素弹出,那么为什么没有一个pop函数可以 即弹出栈顶元素,并且还可以获得栈顶元素呢?
  template<typename T> T stack<T>::pop()
  {
  if(count == 0)
  throw logic_error("stack underflow");
  else
  return data[--count];
  }
  如果函数在后一行抛出了一个异常,那么这导致了函数没有将退栈的元素返回,但是Count已经减1了,所以函数希望得到的栈顶元素丢失了.本质原因是因为这个函数试图一次做两件事,1.返回值,2.改变堆栈的状态.好将这两个独立的动作放到两个独立的函数中,遵守内聚设计的原则,每一个函数只做一件事.我们 再来讨论另外一个异常安全的问题,是很常见的赋值操作符的写法,如何保证赋值操作是异常安全的.
  class Bitmap {...};
  class Widget {
  ...
  private:
  Bitmap *pb;
  };
  Widget& Widget::operator=(const Widget& rhs)
  {
  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
  }
  上面的代码不具备自我赋值安全性,倘若rhs是对象本身,那么将会导致*rhs.pb指向一个被删除了的对象.那么绪改进下.加入证同性测试.
  Widget& Widget::operator=(const Widget& rhs)
  {
  If(this == rhs) return *this; //证同性测试
  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
  }
  但是现在上面的代码依旧不符合异常安全性,因为如果delete pb执行完成后在执行new Bitmap的时候出现了异常,则会导致终指向一块被删除的内存.现在只要稍微改变一下,可以让上面的代码具备异常安全性.
  Widget& Widget::operator=(const Widget& rhs)
  {
  If(this == rhs) return *this; //证同性测试
  Bitmap *pOrig = pb;
  pb = new Bitmap(*rhs.pb); //现在这里即使发生了异常,也不会影响this指向的对象
  delete pOrig;
  return *this;
  }
  这个例子看起来还是比较简单的,但是用处还是很大的,对于赋值操作符来说,很多情况都是需要重载的.