分类: C/C++
2008-05-30 21:00:48
[NextPage]
void f() { throw big(); }
};
int main()
{
X x;
try{
x.f();
}
catch(X::Trouble &)
{
cout<<\"caught Trouble\"<
catch(X::small&)
{
cout<<\"caught small\"<
catch(X::big&)
{
cout<<\"caught big\"<
return 0;
}
//如果是这样的话,抛出的big()类异常则被Trouble类垄断,应该倒着写才可以实现顺序~
捕获所有异常:
有时候,程序员可能希望创建一个异常处理器,使其能够捕获所有的异常情况。用省略号代替异常处理器catch的参数列表就可以实现这一点:
catch(...)
{
cout<<\"an exception was thrown\"<
由于省略号异常处理器能够捕获任何类型的异常,所以最好将它放在异常处理器列表的最后,从而可以避免架空它后面的异常处理器。
省略号异常处理器不允许接受任何参数,所以无法得到任何有关异常的信息,也无法知道异常的类型。它是一个”全能捕获者”。这种catch语句经常用于清理资源并重新抛出所捕获的异常。
重新抛出异常的方法:
是在一个异常处理器内部,使用不带参数的throw语句可以重新抛出异常:
catch(...)
{
cout<<\"an exception was thrown\"<
throw; //该处的throw语句为重新抛出异常到更高一层的语境中去进行异常处理。
}
《不捕获异常》:
1。terminate()函数:
如果没有任何一个层次的异常处理器能够捕获某种异常,一个特殊的库函数 terminate()(在头文件
terminate()函数为void无返回值类型,并且最后要有exit(0)函数表示程序中断而退出。
(!!!注意:决对不允许析构函数抛出异常,因为,如果当一个函数遇到抛出一个异常的时候,入彀改函数有执行构造函数的话,则应该在抛出异常之前先析构该构造的对象,则问题就产生了,于是该函数会返回2个异常,势必引起terminate()函数的调用!!!所以千万不能在析构函数中使用异常抛出!!!
2。set_terminate()函数
通过使用标准的set_terminate()函数,可以设置读者自己的terminate()函数,set_terminate()函数返回被替换的指向terminate()函数的指针(第一次调用set_terminate()函数时,返回函数库中默认的terminate()函数的指针),这样就可以在需要的时候恢复呀的terminate()函数。
自定义的terminate()函数不能有参数,而且其返回值的类型必须是void。另外它必须无条件以某个结束语句是程序终止逻辑。如果terminate()函数被调用,就以为着问题已经无法解决!!!~
以下举例说明terminate()和set_terminate()函数的使用:
#include
#include
using namespace std;
void Myterminate()
{
cout<<\"You have been killed\"<
}
void (*old_terminate)()=set_terminate(Myterminate); //set_terminate()函数的使用,用函数指针来储存值。 [Page]
class Botch
{
public:
class Fruit {};
void f()
{
cout<<\"Botch::f()\"<
}
~Botch()
{
//cout<<\"sasa\"<
}
};
int main()
{
try
{
Botch b;
b.f();
}
catch(...)
{
cout<<\"inside catch(...)\"<
return 0;
}
《清理》
C++的异常处理必须确保当程序的执行流程离开一个作用域的时候,对于属于这个作用域的所有由构造函数建立起来的对象,他们的析构函数一定会被调用。
这里有一个例子,演示了当构造函数没有正常结束时不会调用相关联的析构函数。这个例子还显示了当在创建对象数组的过程中抛出异常时会发生什么情况:
#include
using namespace std;
class Trace
{
static int counter;
int objid;
public:
Trace()
{
objid = counter ++;
cout<<\"Constructing # \"<
throw 3;
}
~Trace()
{
cout<<\"Destructing # \"<
};
int Trace::counter = 0;
int main()
{
try
[NextPage]
{ return p; delete []p; [NextPage] };
Trace n1; //没有异常,完成构造
Trace array[5]; //第0,1号元素得到完全构造,但是之后的元素由于第2号元素构造失败,所以退出构造,第3,4号元素没有构造和析构。
Trace n2; //和上一句一样,由于抛出了异常,所以得不到构造,更加得不到析构。
}
catch(int i)
{
cout<<\"caught \"< }
return 0;
}
程序的输出结果为:
Constructing # 0
Constructing # 1
Constructing # 2
Constructing # 3
Destructing # 2
Destructing # 1
Destructing # 0
caught 3
程序的输出结果充分说明了如果构造函数没有得到正常的完成的话,其析构函数也将得不到调用,并且后面的一系列的对象也将不能继续构造函数。 [Page]
《资源管理》
当我们在使用异常处理机制解决程序中的异常时,我们应当保证当资源由于抛出异常而没有被完全构造时能够调用其析构函数将资源回收,否则由于异常而不能调用析构函数的话,则浪费了构造函数中的一部分资源,造成资源泄露。
以下有一种方法可以解决上述问题,即把所有事物都抽象为对象,使得每次对对象的资源分配具有原子性
,即建立一个模板来使所以要分配资源的事物对象化。如:(其实就是建立了一个中间类当传媒)
#include
#include
// size_t是什么东西呢?我在第一次看到这个动动的时候也是十分的困惑,毕竟以
前没有见过。size_t在
用来保存对象的大小,这一用法是从C语言中借用过来的,现在你应该明白了吧
using namespace std;
template
{
T*ptr;
public:
class RangeError {};
PWard()
{
ptr = new T[sz];
cout<<\"PWard constructor\"<
~PWard()
{
delete []ptr;
cout<<\"PWard destructor\"<
T& operator[](int i) throw(RangeError) //重载operatpr[]
{
if(i>=0 && i
throw RangeError(); // 超出范围,异常。
}
};
class Cat
{
public:
Cat() { cout<<\"Cat()\"<
};
class Dog
{
public:
void * operator new [](size_t)
{
cout<<\"Allocating a Dog\"<
}
void operator delete[](void *p)
{
cout<<\"Deallocating a Dog\"<
}
};
class UseResource
{
PWard
PWard
public:
UseResource()
{
cout<<\"UseResource()\"<
~UseResource()
{
cout<<\"~UseResource()\"<
void f()
{
cats[1].g();
}
};
int main()
{
try
{
UseResource ur; [Page]
}
catch(int )
{
cout<<\"inside handler\"<
catch(...)
{
cout<<\"inside catch(...)\"<
return 0;
}
//程序为Dog分配空间的时候再一次抛出异常,但是这一次Cat数组的对象被成功的清理了,没有出现内存泄露。
《auto_ptr类模板》
auto_ptr类模板是在头文件
例如:
#include
#include
#include
using namespace std;
class TraceHeap
{
int i;
public:
static void * operator new(size_t siz)
{
void * p =::operator new (siz);
cout<<\"yes,it’s address is \"<
}
static void operator delete (void * p)
{
cout<<\"No,it’ll died in \"<
}
TraceHeap(int i):i(i) {};
int GetVal() const { return i;}
int main()
{
auto_ptr
cout<
}
//auto_ptr类模板可以很容易的用于指针数据成员。由于通过值引用的类对象总会被析构,所以当对象被析构的时候,这个对象的auto_ptr成员总是能释放它所封装的原指针。
//!!!!但是这个指针对由于抛出异常而没有完全构造的对象还是不能调用其析构函数.
//当涉及到有关异常抛出的继承类时,继承的可能抛出的错误类型或者大小只能减小或不变,但是不能增大。
<标准异常>
所有的标准异常类归根结底都是从exception类派生的,exception类的定义在头文件
logic_error用于描述程序中出现的逻辑错误,如传递无效的参数。runtime_error运行时错误是指那么无法预料的错误,例如硬件故障或者内存耗尽。logic_error和runtime_error都提供了一个参数类型为std::string的构造函数,这样就可以把消息保存到这2种类型的异常对象中,通过exception::what()函数,可以从对象中得到它所保存的信息。
举例如下:
#include
#include
using namespace std;
class MyError:public runtime_error
{
public:
MyError (const string & msg = \"a\"):runtime_error(msg) {}
};
int main()
{
try
{
throw MyError (\"dawfsadf\"); [Page]
}
catch(MyError &x)
{
cout<
return 0;
}
〈异常规格说明〉
C++提供一种语法来告诉使用者函数所抛出的异常,这样他们就能很方便切=且正确的处理这些异常了。这就是可选的异常规格说明,它是函数申明的修饰符,写在参数列表的后面。
异常规格说明再次使用了关键字throw,函数可能抛出的所有可能异常的类型应该被写在throw之后的括号内。这里的函数申明如下所示:
void f() throw(toobig,toosmall,divzero);
在涉及异常的情况下,传统的函数申明:
void f();
意味着函数可能抛出任何类型的异常。
而下面这个函数:
void f() throw();
则表示不会抛出任何异常。
unexpected()函数(在
如果函数所抛出的异常没有列在异常规格说明的异常集中,则系统将会自动调用函数unexpected(),默认的unexpected()函数会调用前面说讲过的terminate()函数。
set_expected()函数就像前面说的set_terminate()函数一样用。
下面这个例子可以分清这2个函数的使用:
#include
#include
using namespace std;
class up {};
class fit {};
void g();
void f(int i) throw(up,fit)
{
switch(i)
{
case 1:throw up();
case 2:throw fit();
}
g();
}
//void g() {}; //当是这个函数时,将不会报错,因为switch语句返回的2个异常都可以被捕获。
//void g() { throw 47; }//当是这个函数时,将会报错,因为,多返回了一个异常类型,而该异常类型
//却不在f()函数的异常列表范围内,所以引起调用umexpected()函数,报 //错。
void my_unexpected()
{
cout<<\"unexpected exception thrown\"<
}
int main()
{
void (*old_unexpected)()=set_unexpected(my_unexpected);
for(int i=1;i<=3;i++)
try{
f(i);
}catch(up &)
{
cout<<\"Up caught\"<
{
cout<<\"fit caught\"<
return 0;
}
如果unexpected处理器所抛出的异常还是不符合函数的异常规格说明,下列2种情况之一讲会发生:
1。如果函数的异常规格说明中包括std::bad_exception(在
2。如果函数的异常规格说明中不包括std::bad_exception,程序则直接调用terminate()函数退出程序。