在前三篇中我说明了有效创建一个类的前4个考虑步骤,现在就差最后一步了,考虑创建与类定义有关的
异常类。
异常的概述
用户调用某个函数,函数可以在运行时检测到
错误,但是不知道如
何处理;用户呢,实际上知道在遇到这种错误时,该如何处理;为了解决这类问题,提出了异常的概念。异常的基本思想是:当函数检测到自己无法处理的错误时抛出一个异常,以便调用者(用户)能够处理这个异常。用户如果希望处理这种异常可以使用catch捕获这个异常。
传统的错误处理方式
(1)终止程序
(2)返回一个表示“错误”的值
(3)返回一个合法值,让程序处于某种非法状态
(4)调用一个预先准备好,在出现“错误”的情况下用的函数
(1)的方式,在未捕获异常的情况,默认发生的事情,换句话说,跟没有异常一样。但是我们可以做的更好不是吗?(2)调用者需要检查错误值,这容易使程序的体积加大,而且能够正常返回错误值是前提,但有时并不如人所愿。(3)事实上,即使能返回一个合法值,但是调用者往往很难注意到程序已经处在非法状态中了。(4)看上去与
异常处理相似,但是是不是出现这种错误时,就一定要执行这个预先准备好的函数呢?
一个异常就是某个用于表示异常发生类的一个对象。检查到一个错误的代码段throw一个对象。一个catch语句表明它要处理某个异常。一个throw的作用就表示
堆栈的一系列回退,直到找到适当的catch。
异常与资源管理
对于一个资源管理类来说,一旦在获取资源的过程中,或者使用资源的过程中,捕获到异常,需要释放掉自身占有的异常。当然可以使用try, catch语句,但是C++提出的“资源申请即初始化”的方式更优雅,更安全。
异常与new
一旦在使用new时,捕获到异常,那么处理异常的代码中必须调用对应的delete。
异常与资源耗尽
当堆
内存由于申请的空间过大,已经耗尽资源时;C++默认调用_new_handler
函数指针。
void customized_new_handler(){
...
};
set_new_handler(&customize_new_handler); //std func
void f()
{
void (*oldnh)() = set_new_handler(&customized_new_handler);
try {
// ...
}
catch(bad_alloc) {
// ...
}
catch( ... ){
set_new_handler(oldnh); //reset handler
throw; //re-throw
}
set_new_handler(oldnh); //reset handler
}
上述是资源耗尽的一般情况,极端情况可能连new一个异常对象的空间也没有了,那怎么办?不用担心,
C++语言已经帮我们想好了,那就是每一个C++程序实现都要求保留足够的存储,在资源耗尽的情况,仍然可以抛出bad_alloc。
异常与构造函数
因为构造函数其特殊性,无法返回一个独立的值供调用程序检查。异常处理机制允许从构造函数内部传出来错误信息。通常构造函数多与资源申请有关,所以建议采用“资源申请即初始化”的方式处理异常。
异常与成员初始化
将成员初始式包含在try,catch块内。
class X {
Vector v;
//
public:
X(int);
//
};
X::X(int s)
try
:v(s)
{
// ...
}
catch(Vector::Size){
// ...
}
异常与析构函数
析构函数的调用存在两种情况
I. 正常销毁对象,调用
II. 因异常,在捕获异常的处理块中调用;
对于后一种情况,
绝不能让析构函数里抛出异常。如果真是这样,那就是异常处理机制的一次失败,并调用std::terminate()。那么抛出异常退出析构函数也违背了标准库的要求。
如果析构函数必须要调用一个可能抛出的异常的函数,可以通过try, catch块保护自己;当然保护方式要么吞掉所有可能抛出的异常,要么终止程序。如果要求析构函数调用的这个可以抛出异常的函数必须做出运行时异常反应的话,那么请考虑使用一个普通函数。(参考《Effective C++》条款08)
异常的描述
void f() throw( x2, x3); //may throw only x2, x3 exceptions
void f() //can throw any exception
void f() throw(); //no exception thrown
异常的映射
unexpected()的行为与std::bad_exception映射;
与此set_new_handler()类似,对unexpected()的响应由_unexpected_handler决定,它又是通过<exception>中的std::set_unexpected()设置的。
用户
自定义的异常映射
void g() throw(Yerr);
g()被在网络分布式环境下被调用,由于g()对网络异常一无所知,自然调用unexpected()。如果不希望g()调用unexpected(),那么需要g()处理所有网络情况可能抛出的那些网络异常,那么就需要重写g()。如果g()由于种种
限制不能重写,我们只能通过重新定义unexpected()完成。
一、首先采用“资源申请即初始化”方式为unexpected()函数定义一个类
//摘自《The C++ Programming Language》第14.6.3节
typedef void(*unexpected_handler)();
unexpected_handler set_unexpected(unexpected_handler);
class STC { //store and reset class
unexpected_handler old;
public:
STC(unexpected_handler f){ old = set_unexpected(f);}
~STC(){ set_unexpected(old);}
};
二、定义一个函数,使它具有我们希望的unexpected()的
意义
class Yunexpected:public Yerr{};
void throwY() throw (Yunexpected) { throw Yunexpected(); }
三、提供一个网络版g函数
void networked_g() throw (Yerr)
{
STC xx(&throwY);
g();
}
但是上述对于用户而言,只是知道因为调用g()产生一个unexpected异常,具体是什么网络异常并不知道,那么怎么办呢?
这时修改下Yunexpected的类定义和throwY()的定义,使之可以保存真正的异常信息即可。
class Yunexpected:public Yerr{
public:
Network_exception * pne;
Yunexpected(Network_exception *p) : pne(p?p->clone():0){}
~Yunexpected(){delete pne;}
};
void throwY() throw(Yunexpected){
try{
throw; //re-throw
}
catch(Network_exception& p){
throw Yunexpected(&p);
}
catch( ... ){
throw Yunexpected(0);
}
}
未捕获异常
缺省情况下,如果抛出一个异常未被捕获,那就会调用函数std::terminate()。
uncaught_exception由_uncaught_handler决定,uncaught_handler由std::set_terminate()设置。
标准异常体系
标准异常体系的样子如下图所示
其中exception在文件<exception>里给出
class exception{
public:
exception() throw ();
exception(const exception&) throw();
exception& operator=(const exception&) throw();
virtual ~exception() throw();
virtual const char* what() const throw();
private:
// ...
};
所有标准异常都由exception派生,然后不是所有的异常都由exception派生,所以通过捕捉exception想捕获所有异常是错误的想法。
如何定义一个完善的异常类,并且继承于std::exception体系呢?
#include <string>
#include <exception>
class MyBaseException : public std::exception
{
public:
//Constructor without inner exception
xxBaseException(const std::string& what = std::string("xxBaseException"))
: xx_BaseException(0), xx_What(what) {}
//Constructor with inner exception
xxBaseException(const xxBaseException& innerException, const std::string& what = std::string("xxBaseException"))
: xx_BaseException(innerException.clone()), xx_What(what) {}
template <class T> // valid for all subclasses of std::exception
xxBaseException(const T& innerException, const std::string& what = std::string("xxBaseException"))
: xx_BaseException(new T(innerException)), xx_What(what) {}
virtual ~xxBaseException() throw()
{ if(xx_BaseException) { delete xx_BaseException; } }
//don't forget to free the copy of the inner exception
const std::exception* base_exception() { return xx_BaseException; }
virtual const char* what() const throw()
{ return xx_What.c_str(); }
//add formated output for your inner exception here
private:
const std::exception* xx_BaseException;
const std::string xx_What;
virtual const std::exception* clone() const
{ return new xxBaseException(); }
// do what ever is necesary to copy yourselve
};
上述的自定义xxBaseException还是比较简单的,还可以自己添加
文件名,行号等信息,stackTrace深度等信息。
虽然在C++里提供了这样完备的异常处理机制,(似乎每种语言的异常处理机制大致相同,比如JAVA,C#),但是想象一下如果一个函数或者一个类中布满了这样的try, catch块,总归显得代码十分丑陋与笨拙。丑陋的同时也大大降低了开发人员对代码稳定性以及执行效率的自信。
那么怎么才能在避免写很多try,catch块的前提下,又能写出异常安全类或者方法呢?
C++的实现者,Bjarne Stroustrup,和《Effective C++》的作者,Scott Meyers给了我们关于如何写异常安全类的建议:
实现”异常安全“类
说到异常安全类,那么其定义是必须要说的。(定义引自
"Exception Safty:Concepts and Techniques", Bjarne Stroustrup, Advances in Exception Handling Techniques, Lecture Notes in Computer Science 2022. Springer-Verlag, 2001, 60--76, Springer-Verlag)
引用