博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Coding之路——重新学习C++(9):解决异常
阅读量:4688 次
发布时间:2019-06-09

本文共 3421 字,大约阅读时间需要 11 分钟。

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
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 std::auto_ptr{ template
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
auto_ptr(auto_ptr
&a) throw(); auto_ptr& operator=(auto_ptr &a) throw(); template
auto_ptr& operator=(auto_ptr
&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
) throw(); //从auto_ptr_ref复制 template
operator auto_ptr_ref
() throw(); template
operator auto_ptr
() 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()代表不抛出异常。

转载于:https://www.cnblogs.com/xskCoder/p/4004056.html

你可能感兴趣的文章
python 文件单行循环读取的坑(一个程序中,文件默认只能按行循环读取一次,即使写到另一个循环里,它也只读取一次)...
查看>>
IT常用单词
查看>>
拓扑排序
查看>>
NYOJ--32--SEARCH--组合数
查看>>
day07
查看>>
【Android开发:自定义控件系列二】关于PopupWindow的注意点
查看>>
HTML——使用表格进行页面布局
查看>>
字符串统计 连续的某个字符的数量 1.1.4
查看>>
JMS
查看>>
gulpfile 压缩模板
查看>>
JAVA知多少
查看>>
Kruskal算法(转)
查看>>
CSS3 Media Queries实现响应式布局
查看>>
【34.14%】【BZOJ 3110】 [Zjoi2013]K大数查询
查看>>
【 henuacm2016级暑期训练-动态规划专题 A 】Cards
查看>>
第五篇:白话tornado源码之褪去模板的外衣
查看>>
设备常用框架framework
查看>>
bootstrap模态框和select2合用时input无法获取焦点(转)
查看>>
快速转移数据的要领
查看>>
windows情况下的oracle效力
查看>>