栈解旋
异常抛出后,从进入try块起,到异常被处理,这期间在栈上的构造的所有对象都会被自动析构。析构的顺序与构造的顺序相反。这一过程叫做解旋。
【也就是说栈模型先进后出并没有被打破,当异常throw抛掷,栈上的对象会被析构】
#include <iostream> using namespace std; class MyException{}; class Test { public: Test(int a=0,int b=0) { this->a = a; this->b = b; cout<<"构造函数被执行"<<endl; } void printT() { cout<<"a:"<<a<<" "<<"b:"<<b<<endl; } ~Test() { cout<<"Test 析构函数执行"<<"a:"<<a<<"b:"<<b<<endl; } private: int a; int b; }; //一旦声明了抛出的类型,就只能抛出这些声明了的异常 //一般情况下,为了增强程序的可读性,在函数声明的时候列出所有的异常类型 void myFunc()throw(MyException) { Test t1; Test t2(1,2); cout<<"定义了两个变量,异常抛出后测试栈变量如何被析构"<<endl; throw MyException(); } int main(void) { try { myFunc(); } catch(MyException) { cout<<"正在处理异常类型 MyException"<<endl; } catch(...) { cout<<"未知异常类型"<<endl; } return 0; }
异常接口声明
1.为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型。
例如:void func() throw(A,B,C,D)这个函数能够且只能抛出类型ABCD及其子类型的异常
2.如果在函数声明中没有包含异常接口声明,则此函数可以抛掷任何类型的异常
3.一个不抛出任何异常的的函数可以声明为
void func throw();
4.如果一个函数抛出了它的异常接口声明所不允许的异常,unexpected函数会被调用,该函数默认行为调用terminate函数终止程序
异常类型和异常变量的生命周期
1) throw的异常是有类型的,可以是数字、字符串、类对象...
2) throw的异常是有类型的,catch严格按照类型进行匹配
#include <iostream> using namespace std; //throe int void filecopy02(char *filename2,char *filename1) { FILE *fp1 = nullptr; FILE *fp2 = nullptr; fp1 = fopen(filename1,"rb"); if(fp1 == nullptr) { throw 1; } fp2 = fopen(filename2,"wb"); if(fp2 == nullptr) { throw 2; } char buf[256]; int readlen,writelen; readlen=fread(buf,1,256,fp1); while(!feof(fp1)) { writelen = fwrite(buf,1,readlen,fp2); if(readlen != writelen) { throw 3; } } fclose(fp1); fclose(fp2); } //throw string void filecopy03(char *filename2,char *filename1) { FILE *fp1 = nullptr; FILE *fp2 = nullptr; fp1 = fopen(filename1,"rb"); if(fp1 == nullptr) { throw "打开源文件失败"; } fp2 = fopen(filename2,"wb"); if(fp2 == nullptr) { throw "打开目标文件失败"; } char buf[256]; int readlen,writelen; readlen=fread(buf,1,256,fp1); while(!feof(fp1)) { writelen = fwrite(buf,1,readlen,fp2); if(readlen != writelen) { throw "文件拷贝过程失败"; } } fclose(fp1); fclose(fp2); } //throw class class BadSrcFile { public: void toString() { cout<<"aaaaaa"<<endl; } }; class BadDestFile { }; class BadCpyFile { }; void filecopy04(char *filename2,char *filename1) { FILE *fp1 = nullptr; FILE *fp2 = nullptr; fp1 = fopen(filename1,"rb"); if(fp1 == nullptr) { throw BadSrcFile(); } fp2 = fopen(filename2,"wb"); if(fp2 == nullptr) { throw BadDestFile(); } char buf[256]; int readlen,writelen; readlen = fread(buf,1,256,fp1); while (!feof(fp1)) { writelen = fwrite(buf,1,readlen,fp2); if(writelen != readlen) { throw BadCpyFile(); } } fclose(fp1); fclose(fp2); } int main(void) { try { filecopy02("01.txt","02.txt"); } catch(int e) { cout<<"发生异常:"<<e<<endl; } catch(const char *e) { cout<<"发生异常:"<<e<<endl; } catch(BadSrcFile *e) { e->toString(); cout<<"发生异常,打开源文件失败"<<endl; } catch(BadSrcFile &e) { e.toString(); cout<<"发生异常,打开源文件失败"<<endl; } catch(BadDestFile e) { cout<<"发生异常,打开目的文件失败"<<endl; } catch(BadCpyFile e) { cout<<"拷贝文件时发生异常"<<endl; } catch(...) { cout<<"未知异常..."<<endl; } return 0; }
C++编译器通过throw来产生对象,C++编译器在执行对应的catch分支,相当于一个函数应用,把实参传递给形参。【当然会通过解螺旋把栈清空】
不妨把catch看作一个模板函数,会进行严格的类型匹配
异常的层次结构(继承在异常中的应用)
- 异常是类(自己创建的异常类)
- 异常派生
- 异常中的数据:数据成员
- 按引用传递异常
- 在异常中使用虚函数
class eSize { public: eSize(int index) { this->index = index; } virtual void printErr() { } private: int index; }; class eNegative:public eSize { public: eNegative(int x):eSize(x) { } virtual void printErr() { cout<<"index<0"<<endl; } }; class eZero:public eSize { public: eZero(int x):eSize(x) { } virtual void printErr() { cout<<"index=0"<<endl; } }; class eTooBig:public eSize { public: eTooBig(int x):eSize(x) { } virtual void printErr() { cout<<"index>10000"<<endl; } }; class eTooSmall:public eSize { public: eTooSmall(int x):eSize(x) { } virtual void printErr() { cout<<"index<10"<<endl; } };
标准程序库异常
C++标准提供了一组标准异常类,这些类以基类Exception开始,标准程序库抛出的所有异常,都派生于该基类,这些类构成如下图的异常类的派生继承关系。该基类提供一个成员函数what()用于返回错误信息(返回类型为const char*)。在Exception类中,what()函数声明如下:
virtual const char * what()const throw();
上面包含了各个具体异常类的含义以及它们的头文件。runtime_error和logic_error是一些具体的异常类的基类,它们分别表示两大类异常。logic_error表示那些可以在程序中被预先检测到的异常,也就是说如果小心得编写程序,这些类异常能够避免,而runtime_error则表示那些难以被预先检测的异常。
一些编程语言规定只能抛掷某个类的派生类(例如java中允许抛掷的类必须派生自Exception类),C++虽然没有这项强制的要求,但仍然可以选择这样实践。例如:在程序中可以使得所有抛出的异常皆派生自Exception(或者直接抛出标准程序库提供的异常类型,或者从标准程序库提供的异常类派生出新的类),这样会带来很多方便.(因为多态)
logic_error和runtime_error两个类及其派生类,都有一个接收const string &型参数的改造函数。在构造异常对象时需要将具体的错误信息传递给该函数,如果调用该对象的what函数,就可以得到构造时提供的错误信息。
#include <iostream> #include <stdexcept> using namespace std; class Teacher { public: Teacher(int age) { if(age > 100) { throw out_of_range("年龄太大"); } this->age = age; } private: int age; }; class Dog { public: Dog() { p = new int[1024*1024*100]; //4MB } private: int *p; }; int main(void) { // try // { // Teacher t1(30); // } // catch(exception &e) // { // cout<<e.what()<<endl; // } try { Dog *pdog; for(int i=1;i<=10000000;i++) { pdog = new Dog(); } } catch(exception& e) { std::cerr << e.what() << '\n'; } return 0; }