偶然间读到大神的著作,了解到异常处理也是程序设计中非常重要的一块,略有收获,遂记录之,以备查用。现有如下简单代码,vs2010测试通过

 1.对象被抛出作为异常时,总会发生拷贝赋值,且临时对象拷贝以所抛出对象的静态类型为准

 
  
  1. // 异常抛出与函数调用的差异.cpp: 主项目文件。  
  2.  
  3. #include "stdafx.h"  
  4. #include<iostream>  
  5. #include<string>  
  6. using namespace std;  
  7. using namespace System;  
  8. class Widget  
  9. {  
  10. public:  
  11.     Widget(int ID):id(ID)  
  12.     {  
  13.         cout<<"Widget构造函数被调用"<<endl;  
  14.     }  
  15.     Widget(Widget &w)  
  16.     {  
  17.         this->id=w.id;  
  18.         cout<<"Widget拷贝构造函数被调用"<<endl;  
  19.     }  
  20.     friend ostream& operator<<(ostream &os,Widget &w);//定义友元函数 重载<<  
  21.     ~Widget()  
  22.     {  
  23.         cout<<"Widget析构函数被调用"<<endl;  
  24.     }  
  25. private:  
  26.     int id;  
  27. };  
  28. ostream& operator<<(ostream &os,Widget &w)  
  29. {  
  30.     os<<w.id;  
  31.     return os;  
  32. }  
  33. class SpecialWidget:public Widget  
  34. {  
  35. public:  
  36.     SpecialWidget(int id,string Name):Widget(id),name(Name)  
  37.     {  
  38.         cout<<"SpecialWidget构造函数被调用"<<endl;  
  39.     }  
  40.     ~SpecialWidget()  
  41.     {  
  42.         cout<<"SpecialWidget构造函数被调用"<<endl;  
  43.     }  
  44. private:  
  45.     string name;  
  46. };  
  47.  
  48. void passAndThrowWidget()  
  49. {  
  50.     Widget localWidget(1); //创建局部对象  
  51.     cout<<localWidget;  
  52.     cout<<endl;  
  53.     throw localWidget; //此处的localWidget在函数抛出时析构掉,而为catch制作了一个localWidget的副本  
  54. }  
  55. int main(array<System::String ^> ^args)  
  56. {  
  57.     try 
  58.     {  
  59.     passAndThrowWidget();  
  60.     }  
  61.     catch(Widget w)//接收localWidget接收的副本,因为此处是传值,会再次制作副本的副本  
  62.     {  
  63.         cout<<"捕获异常"<<endl;  
  64.     }  
  65.     system("pause");  
  66.     return 0;  
  67. }  

vs2010输出结构如下图所示:

 

由此可见全局函数passAndThrowWidet的调用将导致产生三个构造函数的调用,第一次是局部对象的生成,调用普通构造函数;随后局部对象调用拷贝构造制作副本对象;再然后catch使用传值,又一次调用拷贝构造函数。总共三次构造,三次析构。

如果将catch中的形参改为引用,势必会减少第三次的拷贝函数调用,其该后运行效果图如下:

 现在我们来修改一下passAndThrowWidget函数:

 

 
  
  1. void processAndThrowWidget()  
  2. {  
  3.   SpecialWidget localSpecialWidge(45,"SpecialWidget");//构造派生类对象  
  4. cout<<localWidget<<endl;  //调用友元全局函数
  5. Widget &w=localSpecialWidge;  //使用派生类对象初始化基类引用
  6. throw w;  //w虽然是派生类的引用,但制作临时对象时,以静态类型为准,故此产生基类拷贝构造调用
  7. int main()
  8. {
  9. try
      {
        passAndThrowWidget();
      }
      catch(Widget &w)//接收localWidget接收的副本,因为此处是传值,会再次制作副本的副本
      {
       cout<<"捕获异常"<<endl;
      } }

此次运行结果如下图所示:

 

2.throw抛出的类型和catch接收类型的匹配问题(只存在两种类型的自动转化匹配,继承和无形指针)

现有如下代码:

 
  
  1. #include"stdafx.h"  
  2. #include<iostream>  
  3. using namespace std;  
  4. void f(int x)  
  5. {  
  6.     cout<<"全局函数f调用"<<endl;  
  7.     throw x;//抛出×××异常  
  8. }  
  9. void main()  
  10. {  
  11.     try 
  12.     {  
  13.         f(3);  
  14.     }  
  15.     catch(double)  
  16.     {  
  17.         cout<<"throw抛出的int类型不会被catch的double类型捕获"<<endl;  
  18.     }  
  19.     catch(int)  
  20.     {  
  21.         cout<<"捕获"<<endl;  
  22.     }  
  23.     system("pause");  

运行效果图如下:

 

当f抛出异常时,会匹配int型的catch,如果将catch(int)后继部分去掉,程序会发生异常终止,即int型的异常未能向函数调用那样存在类型转换问题。

但是当涉及到继承和无形指针时,throw,catch存在类型的自动转换。此时catch中的基类型(无论是引用,指针或是传值)都能够捕获throw各种派生类型的异常。

其次,catch(const void *)无型指针可以不过throw出来的各种类型指针。

最后,一个try块后可以跟多个catch块,catch的异常捕获遵循的原则是最先吻合,不同于虚函数中的最佳吻合。

总结:第一,异常对象总是会被复制(基于抛出对象的静态类型),如果以传值方式捕获,其甚至被复制两次。第二,被抛出成为异常的对象,其被允许的类型转换动作只针对继承和无形指针两种情况。第三,catch捕获遵循最先吻合原则。