C/C++程序设计
1
异常 (exception)是程序控制中的偶发事件。异常的来源分两种:一种是硬件异常如 CPU触发的异常,一种是软件异常。软件异常是程序设计不周操作次序不当引发的意外。
异常处理就是关于不期望的事件发生后进行妥当应付的方法。 C++异常处理机制提供了捕获各种数据类型不测消息的能力。
C/C++程序设计
2
四、异常的重新抛出五、对象的清理七、关于抛出异常的函数显式说明
C/C++程序设计
3
四、异常的重新抛出
C++异常处理的嵌套结构中,其规则是外层的 throw语句投出的异常流入外层的 catch捕获器,而内部 catch块接受的是同级 try块 throw语句抛来的信息。
如果程序员期望内层触发的错误源输送到高层专门的程序段予以处理,而不是当前层的 catch块中解决,需要使用异常的层层转递手段,这就是 throw语句不带表达式的默认形式。
C/C++程序设计
4
不带表达式的 throw语句内嵌在 catch内,意味着当前
catch块在入口中捕获的类型信息,throw接力地将此类型信息抛出到上层的相应类型入口的 catch处理器。
特定异常流就从内层中向外传到需要的地方。
throw语句应与同级的 catch块平衡。
C/C++程序设计
5
[例 ] 异常的重新抛出
# include <stdio.h>
enum { eLong,eUnknown,eSpecial };
struct Unknown { };
class Special { };
void fa (int kind)
{ try
{ if (kind==eSpecial) throw Special();
if (kind==eUnknown) throw Unknown();
}
C/C++程序设计
6
catch (Special)
{ printf (“fa Rethrowing Special Exception out\n”);
throw;
}
catch(...)
{ printf ("fa Rethrowing Unknown Exception out\n");
throw;
}
printf ("normal process in fa\t");
}
C/C++程序设计
7
void fb(int kind)
{ try
{ fa(kind); }
catch (Special)
{ printf ("fb Rethrowing Special Exception out\n");
throw;
}
catch(...)
{ printf ("Unknown Exception treated here in fb\n");}
printf ("normal process in fb\n");
}
C/C++程序设计
8
void fc(int kind)
{ try
{ fb (kind);
if (kind==eLong) throw (long)kind; }
catch (long)
{ printf ("long type Exception is in fc \n"); }
catch (Special)
{ printf ("Special Exception is treated here in fc \n");
}
}
void main()
{ fc (eLong); fc (eUnknown); fc (eSpecial); }
C/C++程序设计
9
程序运行输出结果:
normal process in fa normal process in fb
long type Exception is in fc
fa Rethrowing Unknown Exception out
Unknown Exception treated here in fb
normal process in fb
fa Rethrowing Special Exception out
fb Rethrowing Special Exception out
Special Exception is treated here in fc
C/C++程序设计
10
五、对象的清理在函数的调用点,编译器建立一个动态的堆栈空间,被调函数的局部数据、形参以及函数的返回地址 (函数调用点下一条语句的地址 )随着被调函数的激活而瞬间树立在相关联的堆栈空间中,这是堆栈空间升涨的过程。
当函数遇到 return语句时,堆栈空间根据先进后出的原则一一地弹出局部数据占有的内存空间,这是堆栈降落
(stack unwinding)的过程。
C/C++程序设计
11
嵌套调用函数堆栈空间涨高,从一个函数中返回堆栈空间落低。对于不含 throw语句的堆栈涨落,其幅度是相对定态地取决于一个被调函数的堆栈高度。如果 throw语句从内层嵌入的被调函数抛出异常到间接的主控函数,这其中涵盖的堆栈高度动态地决定于异常触发点离 catch捕获器的位置差距。
throw语句,goto语句和 return语句都导致控制流程的转向,对于程序块中的局部对象在控制转向的撤离点,编译器会仔细安插相应析构函数的调用。
throw语句的异常转向和在用其它 if语句下的 return退出机制非常相近。
C/C++程序设计
12
return卷入的清理工作同样会放进 throw的动态撤离环节,return不能自动释放的堆中资源,throw一样的力所不及。
函数嵌套调用中动态建立的堆栈空间的降落过程在
return触发的场合和 throw抛出的情景基本上是相仿的,
return将流程的控制权直接交给调用点的下一个语句,而
throw将控制权投掷到类型匹配的平行的 catch入口。
其间可能涉及跨多个函数段的调转,此时堆栈的降落梯度更大,调用的析构函数更多。
特别当 throw直接抛出一个数值对象表达式,涉及该对象的构造、析构以及 catch接受点对象的拷贝重新压入堆栈,这些复杂的过程编译器很难做到百无一失。
C/C++程序设计
13
如果并无跨函数段的异常抛出,throw语句的转移则近似于 goto语句的向后调转,但 goto语句具有前跳的功能因而可适当回归流程的原处。
throw语句胜过 goto语句的地方在于可以跨越被调函数和主控函数直接传递 throw之后的异常信息,此种异常信息的跨函数传递必然伴随编译器内部的跟踪机制,代价是上千条编译器预埋的不为程序员察知的指令。
静态对象、全局对象和堆中对象不在 stack堆栈空间中,因此编译器不负责这些对象的自动撤销工作,Heap对象的删除需要程序员仔细负责。
C/C++程序设计
14
下面的例子显示对象的转送和清理情况。
[例 ] 异常过程中对象的清除
#include<stdio.h>
class ClassB
{ public,int m_b;
ClassB (int n=1)
{ m_b=n;
printf ("%d.ClassB::ClassB(),this=%p\n",m_b,this);
}
~ClassB()
{ printf ("%d.ClassB::~ClassB()\n",m_b); }
};
C/C++程序设计
15
class ClassC
{ public,int m_c;
ClassC (int n=2)
{ m_c=n;
printf ("%d.ClassC::ClassC(),this=%p\n ",m_c,this);
}
~ClassC()
{ printf ("%d.ClassC::~ClassC()\n",m_c); }
void Show()
{ printf ("Show m_c=%d,this=%p \n",m_c,this); }
};
enum { eObjc=888,eObjh=3 };
C/C++程序设计
16
void fa (int k)
{ if (k==eObjc)
{ printf ("send local Objc with k=%d\n",eObjc);
throw ClassC(k);
}
ClassB objb(2);
ClassC *pObjc=new ClassC(k);
printf ("send Heap obj with k=%d\n",k);
throw pObjc;
printf ("this code area is dead block\n");
delete pObjc;
}
C/C++程序设计
17
void fb (int k)
{ printf ("Enter regular routine with k=%d\n",k);
try
{ ClassB objb(1);
fa(k);
}
catch (ClassC objc )
{ printf ("local Objc received\n");
objc.Show ();
}
C/C++程序设计
18
catch (ClassC *p=NULL )
{ if (p!=NULL)
{ printf ("Heap Obj received\n");
p->Show ();
delete p;
}
}
}
void main()
{ fb (eObjc); fb (eObjh);
}
C/C++程序设计
19
fb (eObjc)运行输出结果如下:
Enter regular routine with k=888
1.ClassB::ClassB(),this=0063FDF8
send local Objc with k=888
888.ClassC::ClassC(),this=0063FDB4
888.ClassC::~ClassC()
1.ClassB::~ClassB()
local Objc received
Show m_c=888,this=0063FDDC
888.ClassC::~ClassC()
888.ClassC::~ClassC()
C/C++程序设计
20
fb (eObjh)运行输出结果如下:
Enter regular routine with k=3
1.ClassB::ClassB(),this=0063FDF4
2.ClassB::ClassB(),this=0063FD9C
3.ClassC::ClassC(),this=00760EE0
send Heap obj with k=3
2.ClassB::~ClassB()
1.ClassB::~ClassB()
Heap Obj received
Show m_c=3,this=00760EE0
3.ClassC::~ClassC()
C/C++程序设计
21
函数调用 fb(eObjc)输出结果说明构造函数和相应的析构函数调用未成明显的反对称的次序,这表面上违背 C++标准关于析构函数以与构造函数相反的次序调用的规定。
抛出异常语句 throw ClassC(k)诞生一个无名局部对象地址为 this=0063FDB4,catch(ClassC objc)将该对象在上层主控函数中加以接受,但对象的地址已经变动为
this=0063FDDC,因此获得的是无名对象的拷贝。
无名对象构造和析构是及时在被调函数中反序进行的。
C/C++程序设计
22
将无名对象抛到主控函数时编译器应该进行了构造函数的两次暗中调用,以平衡 catch处理器触发的两次析构。
函数调用 fb(eObjh)输出结果满足了构造函数和析构函数反序调用和次数平衡要求。
堆中对象的传递是通过对象指针实施的,对象指针是简单的 4字节变量,因此 throw pObjc投掷到相应捕获器
catch(ClassC *p ) 入口形参 p。
因此 p获得的初始值即等于抛过来的 pObjc两者是一样的都为 this=00760EE0。
C/C++程序设计
23
六、类层次的异常处理策略
try~catch控制结构作为 C++语言的一部分,是对 C语言流程控制的有力充实和发展,特别 throw的转向作用可以有机地游刃于原先 C语言的 switch结构,if~else结构中。可以将 try~catch控制结构当作一个有效的多路分支结构看待而不仅仅只是处理异常。
此种异常处理在面向对象的编程中起着特殊的作用。对于类的继承关系,C++语言照顾多态机制的运转,专门指定向上映射的特殊规则。这就是派生类对象指针可隐含类型转换支付给基对象指针,派生类对象也可隐含地被基对象拦腰切断。
C/C++程序设计
24
派生类的对象可以抛入基对象入口的 catch处理器,派生类的对象指针可以初始化 catch为基对象指针的入口。以维持多态类动态绑定的幕后运转发生作用。因此在安排
catch处理器的先后次序时应考虑多态类的向上映射规则的效应。
形如 catch(CBase b)格式的接收基对象处理器能够包容
throw CDeried()抛出的派生类对象,反之则不然。
因此 catch(CDeried d) 格式的捕获派生类对象的处理器放置在 catch(CBase b)的前面,否则编译器提出警告提示,
以免派生类的异常被基对象的处理器捕获。
C/C++程序设计
25
[例 ] 继承层次 catch处理器的先后次序
# include<stdio.h>
class B
{ public,int m_b;
B (int n=1)
{ m_b=n;printf ("B::B () this=%p\n",this); }
virtual ~B ()
{ printf ("B::~B () this=%p\n",this); }
virtual int HandleError ();
};
C/C++程序设计
26
class C,public B
{ public,int m_c;
C() { m_c=2;printf ("C::C () this=%p\n",this); }
virtual ~C() { printf ("C::~C () this=%p\n",this); }
virtual int HandleError ();
};
class D,public C
{ public,int m_d;
D() { m_d=4; printf ("D::D () this=%p\n",this); }
virtual ~D(){printf ("D::~D() this=%p\n",this);}
virtual int HandleError ();
};
C/C++程序设计
27
int B::HandleError ()
{ printf ("B::HandleError () this=%p\n",this);
return 1; }
int C::HandleError()
{ printf ("C::HandleError () this=%p\n",this);
return 0; }
int D::HandleError()
{ printf ("D::HandleError () this=%p\n",this);
return 1; }
enum
{ classb,classc,classd,etype1,etype2 };
int ErrorInfo (int k)
C/C++程序设计
28
{ switch (k)
{ case etype1,return etype1;
case etype2,return etype2;
default,return etype2+1; } }
void ThrowHandler (int k)
{ switch (k)
{ case classb,throw B();
case classc,throw C();
case classd,throw D();
default,throw ErrorInfo (k);
}
}
C/C++程序设计
29
void VirtualHandling (int k)
{ try { ThrowHandler (k); }
catch (B & rBase) { rBase.HandleError (); }
catch (int k)
{ switch (k)
{ case etype1,printf ("etype1 Error treated here\n");
break;
case etype2,printf ("etype2 Error treated here\n");
break;
default,printf ("a type Error treated here\n");
break; }
}
}
C/C++程序设计
30
void DerivedFirst (int k)
{ try { ThrowHandler(k); }
catch(D d) { d.HandleError(); }
catch(C c) { c.HandleError(); }
catch(B b) { b.HandleError(); }
catch(int ) { printf ("a int Error treated here\n"); }
}
void main()
{ VirtualHandling (classd);
DerivedFirst (classc);
}
C/C++程序设计
31
VirtualHandling(classd)运行输出结果如下:
B::B()this=0065FD08
C::C()this=0065FD08
D::D()this=0065FD08
D::HandleError()this=0065FD08
D::~D()this=0065FD08
C::~C()this=0065FD08
B::~B()this=0065FD08
C/C++程序设计
32
DerivedFirst(classc)运行输出结果如下:
B::B()this=0065FD00
C::C()this=0065FD00
C::HandleError()this=0065FD74
C::~C()this=0065FD74
B::~B()this=0065FD74
C::~C()this=0065FD00
B::~B()this=0065FD00
C/C++程序设计
33
说明:
调用 VirtualHandling(classd)导致虚函数的动态绑定,抛出的是无名对象但这个对象的生存期在异常机制中跨函数不变,是值得关注的现象。
调用 DerivedFirst (classc)有所不同,捕获器的入口形参是对象数值形参。对象数值形参的入口涉及对象的拷贝,
因此捕获器获得的对象是原来对象的拷贝,对象的地址发生变动。
C/C++程序设计
34
七、关于抛出异常的函数显式说明早期的编译器中为了增加程序的可读性且与程序员更好的对话,当函数包含 throw语句时,可以一五一十地表明该函数抛出的异常的类型,以便编译器扫描处理时能够格外的细致。
抛出异常的函数 f显式说明是一个清晰的提示,这是一个内涵 throw语句的函数。
C/C++程序设计
35
抛出异常的函数 f显式说明的格式为:
class ClassB{};
class ClassC{};
extern void f (int) throw (int,char*,ClassB,ClassC*);
void f (int k) throw (int,char*,ClassB,ClassC*)
{ if (k==1) throw (int)1;
if (k==2) throw (char*)"1";
if (k==3) throw ClassB();
if (k==4) throw new ClassC();
}
C/C++程序设计
36
但此种后置修饰语法在 MS VC6.0 中反而提出警告
warning,C++ Exception Specification ignored。
也就是说新的编译器已经足够长进,可以应付异常的类型处理,而无须画蛇添足进行明显的说明。
特别地 throw ClassB()既可以匹配数值对象也可以呼应引用对象,因此不多此一举倒不失为上策。
C/C++程序设计
37