首页 > 代码库 > Coding之路——重新学习C++(9):解决异常

Coding之路——重新学习C++(9):解决异常

1.什么是异常

  (1)异常的基本思路是让一个函数发现自己无法解决的错误时抛出异常,让调用者来解决。异常处理机制类似于编译时的类型检查和歧义性控制在运行时的对应物,它是一种非局部的控制结构,在抛出异常时,我们用堆栈回退来找到能处理异常的上层函数。有人把异常想象成程序中那些无法挽回的重大错误,但是异常通常代表的是“系统的某些部分不能完成要它做的事”。

  (2)在我们用catch捕捉异常的时候,我们用实际参数的值对形参初始化,这可能造成我们抛出的异常因为捕捉而被切割,所以我们经常用指针和引用捕捉异常来保证异常信息的完整性。

  (3)我们需要知道,异常抛出的时候将被复制,处理器得到的只是一个异常的副本。一般在一个处理函数中无法解决所有异常时,我们就会将异常重新抛出,让异常在合适的地方被处理。注意,无论我们catch异常时异常有没有被切割,我们重新抛出的异常永远是异常最开始被抛出时的原始类型。

2.资源管理

  (1)资源申请即初始化。如何正确的申请资源和释放资源在编程中是一个很重要的问题,一般我们适当的利用带有构造函数和析构函数的对象来处理资源的申请和释放,因为我们在抛出异常时,会进行堆栈回退(“向上穿过堆栈”去为某个异常查找对应处理器的过程),这时所有构造的局部对象都会调用析构函数,从而保证资源的正确释放。

class File_ptr{    File *p;public:    File_ptr(const char *n, const char *a){p = fopen(n, a);}    File_ptr(File *pp){p = pp;}    ~File_ptr(){fclose(p);}    operator FILE*(){return p;}};

这种使用方式最普通的资源就是存储,不过可能造成“存储流失”:

class Y{    int *p;    void init();public:    Y(int s){p = new int[s]; init();}    ~Y(){delete [] p;}};

如果init()抛出异常,那么相关对象的构造就没有完成,对它不会调用析构函数,分配的存储也不会被释放。一种安全的变形是:

class Z{    vector<int> p;    void init();public:     Z(int s):p(s){init();}     //...};

  (2)智能指针auto_ptr。auto_ptr支持“资源申请即初始化”技术,另外,auto_ptr具有与常规指针不一样的复制语义:在将auto_ptr复制给另一个之后,原来的auto_ptr将不再指向任何东西。因为复制auto_ptr的复制需要对本身的修改,所以const auto_ptr不能复制,auto_ptr的破坏性复制也让它不满足标准容器和标准算法。auto_ptr在<memory>中声明,描述如下:

template<class X> class std::auto_ptr{    template<class Y> struct auto_ptr_ref{/*...*/}; //辅助类    X *ptr;public:        typedef X element_type;    explicit auto_ptr(X *p = 0) throw(){ptr = p;}    ~auto_ptr(){delete ptr;}        //注意,复制构造函数和赋值都用非const参数    //以下4个都是先复制,然后a.ptr=0    auto_ptr(auto_ptr &a) throw();            template<class Y> auto_ptr(auto_ptr<Y> &a) throw();    auto_ptr& operator=(auto_ptr &a) throw();    template<class Y>         auto_ptr& operator=(auto_ptr<Y> &a) throw();      X& operator*() const throw(){return *ptr;}     X* operator->() const throw() {return ptr;}     X* get() const throw{return ptr;}                   //提取指针     X* release() throw(){X *t = ptr;ptr = 0;return t;}//放弃所有权     void reset(X *p = 0) throw(){         if(p != ptr) {delete ptr; ptr = p;}    }     auto_ptr(auto_ptr_ref<X>) throw();      //从auto_ptr_ref复制     template<class Y>        operator auto_ptr_ref<Y>() throw();     template<class Y>        operator auto_ptr<Y>() throw();};

  (3)new和异常。下面是标准库的operator new的一个简单实现:

void* operator new(size_t size){    for(;;){        if(void *p = malloc(size)) return p;        if(_new_handler == 0) throw bad_alloc();        _new_handler();    }}

当operator new找不到存储的时候,就会调用_new_handler函数去寻找合适的空间,如果还是找不到的话只能抛出异常。_new_handler是一个函数指针。一般如果想让自己定义的函数成为_new_handler(),那么需要调用set_new_handler函数,set_new_handler函数接收一个void(*p)()类型的指针。

  (4)未预期的异常。如果我们遭遇未预期的映射,我们可以用std::unexcepted()函数抛出bad_exception。它由set_unexcepted函数设定。

  (5)未捕捉的异常。若果一个异常没有被捕捉,会调用std:terminate()函数,它由std::set_terminate()函数设定。

3.异常的声明。

  (1)异常的声明清晰的告诉我们函数将抛出什么异常,而且异常的声明属于界面,可以在不知道函数的具体实现时知道抛出的异常。

  (2)如果一个函数有一个声明包含了异常描述,那么这个函数的每个声明的异常描述都要一致。

  (3)要覆盖一个虚函数,这个函数所带的异常描述必须至少和那个虚函数的异常描述一样受限制。

class B{public:        virtual void f();    virtual void g() throw(X, Y);    virtual void h() throw(X);};class D:public B{public:        void f() throw(X);       //ok     void g() throw(X);      //可以,D::g()比B::g()更受限制    void h() throw(X, Y);  //错误,D::h()没有比B::h()受限制};

函数指针也是如此,你可以用一个更受限制的函数指针给一个没那么受限制的函数指针赋值。所以不能用没有异常描述的函数指针给有异常描述的函数指针赋值。另外,在对函数指针使用typedef时不能带有异常描述,因为异常描述不是函数类型的一部分。

  (3)没有异常描述代表函数可以抛出任何异常,而throw()代表不抛出异常。

Coding之路——重新学习C++(9):解决异常