文章目录
setjmp&longjmp
使用c标准库中的setjmp和longjmp来进行跳转,进而实现异常的处理
#include <iostream>
#include <csetjmp>
using namespace std;
class Rainbow
{
public:
Rainbow() { cout << "Rainbow()" << endl; }
~Rainbow() { cout << "~Rainbow()" << endl; }
};
jmp_buf kansas;
void oz()
{
Rainbow rb;
for (int i = 0; i < 3; i++)
{
cout << "There is no RainBow here" << endl;
longjmp(kansas, 47);
}
}
int main(int argc, char **argv)
{
/*
setjmp在第一次调用的时候会将但钱处理的所有变量,数据保存到jmp_buf中
当setjmp和logjmp使用同一个jmp_buf时,函数进程就会返回当时第一次调用setjmp的位置
并返回logjmp的第二个参数
缺点:
* 当跳出对象的作用范围时,对象不会调用其析构函数,即不会释放空间
*/
if (setjmp(kansas) == 0)
{
cout << "tornado,witch,munchkins" << endl;
oz();
}
else
{
cout << "Anue Em!"
<< "I had a strange dream" << endl;
}
system("pause");
}
缺点:
和goto函数一样,都不会在离开对象的作用范围时对对象进行调用析构函数将其进行释放。
throw抛出异常:
可以使用throw来进行异常的抛出,throw后面跟上要抛出的异常对象。
#include <iostream>
using namespace std;
class Rainbow
{
public:
Rainbow() { cout << "Rainbow()" << endl; }
~Rainbow() { cout << "~Rainbow()" << endl; }
};
void oz()
{
Rainbow rb;
for (int i = 0; i < 3; i++)
{
cout << "There is no RainBow here" << endl;
throw 99;
}
}
int main(int argc, char **argv)
{
try{
cout<<"tornado,witch,munchkins"<<endl;
oz();
}
catch(int){
cout<<"Anue Em!"<<"I had a strange dream"<<endl;
}
system("pause");
}
就是将上面的代码中的jmp函数进行了替换。
捕获异常,异常处理器
在主函数中的try和catch语句块
try语句块在运行时,如果出现异常,则会在catch异常处理表中进行查找,找到第一个符合的异常对象就执行相应的catch语句,然后就退出try语句,继续向下执行。
- 异常类型: 一般传入引用
- 如果按值传递的话,会出现向上转型进行对象切片的操作。
catch(…)
异常类型输入为...
时,表名匹配所有的异常类型。(一般放在最后一条catch语句,因为对catch的匹配时按顺序进行的,当有一个满足了就不会继续向下匹配了。
不捕获异常
如果try块之后的异常处理器不能匹配所抛出的异常,则这个异常就会被传递到位于更高一层的语境中的异常处理,直到某一层能够处理该异常。
terminate()
没有任何一层能够匹配发生的异常,当包含了<exception>库的时候,默认情况下,enrminate调用标准的c语言库函数abort()是程序执行异常终止而退出。在Unix系统下执行abort()程序不会正常终止退出,就是全局对象和静态对象不会自动调用析构函数,相应的空间就不会进行释放。
set_terminate()
可以自定义terminate()函数,调用set_terminate()返回值为指向terminate函数的指针。自行定义的terminate()不能传递参数,不能有返回值,不能抛出异常,要自行写出逻辑结束的语句,当自己调用原先的terminate()就相当于没有自定义了。
#include<iostream>
#include<exception>
using namespace std;
void terminator()
{
cout<<"I'll be back"<<endl;
exit(0);
}
void(*old_terminate)() = set_terminate(terminator);
//old_terminate:指向函数的指针
class Botch
{
public:
class Fruit{};
void f()
{
cout<<"Both::f()"<<endl;
throw Fruit(); //这里抛出异常
}
~Botch(){ throw 'c'; }
};
int main(int argc,char** argv){
try
{
Botch b;
b.f();}
catch(...){//虽然传递的是...可以匹配所有的异常,表面上不会调用terminate
//在处理一个异常的时候会释放栈上发呢配的对象,就会调用相应的析构函数
//调用析构函数有迫使出现另一个异常,迫使程序调用terminate函数
cout<<"inside catch(...)"<<endl;
}
system("pause");
}
栈反解
当try语句块中调用了函数A,函数A调用了函数B,而在函数B中出现了错误,则会进行和平时不同的出栈操作。
一般情况下出栈,是一个函数运行到其return语句时,就返回。而栈反解的操作就是:并不是在第一个函数return语句处返回,而会继续出栈,知道运行到try的语句函数中,才进行返回。
清理资源
#include <iostream>
#include <cstddef>
using namespace std;
template <class T, int sz = 1>
class PWrap
{
T *ptr;
public:
class RangeError
{
};
PWrap()
{
ptr = new T[sz];
cout << "PWrap constructor" << endl;
}
~PWrap()
{
delete[] ptr;
cout << "PWrao destructor" << endl;
}
T &operator[](int i) throw(RangeError)
{
if (i >= 0 && i < sz)
return ptr[i];
throw RangeError();
}
};
class Cat
{
public:
Cat()
{
cout << "Cat()" << endl;
}
~Cat()
{
cout << "~Cat()" << endl;
}
void g() {}
};
class Dog
{
public:
void *operator new[](size_t)
{
cout << "Allocating a Dog" << endl;
throw 1;
}
void operator delete[](void *p)
{
cout << "deallocating a Dog" << endl;
::operator delete[](p);
}
};
class UseResources
{
PWrap<Cat, 3> cats;
PWrap<Dog> dog;
public:
UseResources() { cout << "UseResources()" << endl; }
~UseResources() { cout << "~UseResources()" << endl; }
void f() { cats[1].g(); }
};
int main(int argc, char **argv)
{
try
{
UseResources ur;
}
catch (int)
{
cout << "inside handler" << endl;
}
catch (...)
{
cout << "insider catch(...)" << endl;
}
system("pause");
}
Dog在调用构造函数的时候,会抛出异常,在dog构建之前,为cats创建了3个对象空间。当抛出异常的时候,会释放该堆上的3个cat对象占用的空间。
即:构造函数在抛出异常之前完成,这些对象的析构函数在栈反解的时候也会被调用
函数级的try块
在类的构造函数之后可以使用变量表来初始化对象,此时可以在冒号前面加上try来实现函数级别的try
class Object
{
int i;
public:
Object(const int& kk) try:i(kk){
throw 1;
}catch(...)
{
eixt(0);
}
};
int main()try{
cout<<"Main()"<<endl;
}catch(...)
{
eist(0);
}
上面的main函数后面不含有像普通函数中的括号+函数体的形式。
这就是c++允许所有函数中使用函数级try语句块。
auto_prt
c++的标准中会封装一个RAII的封装类,用于封装指向堆内存的指针,这样可以使程序会自动释放内存。
#include <iostream>
#include <memory>
#include <cstddef>
using namespace std;
class TraceHeap
{
int i;
public:
void *operator new(size_t siz)
{
void *p = ::operator new(siz);
cout << "Allocating TraceHeap object on the heap"
<< "at address " << p << endl;
return p;
}
void operator delete(void *p)
{
cout << "deleting TraceHeap object at address" << p << endl;
::operator delete(p);
}
TraceHeap(int i) : i(i) {}
int getVal() const { return i; }
// virtual ~TraceHeap() { cout << "TraceHeap is free" << endl; }
};
class P
{
public:
void *operator new(size_t siz)
{
void *p = ::operator new(siz);
cout << "Allocating P object on the heap"
<< "at address " << p << endl;
return p;
}
void operator delete(void *p)
{
cout << "deleting P object at address" << p << endl;
::operator delete(p);
}
P(int i){}
};
int main(int argc, char **argv)
{
auto_ptr<TraceHeap> pMyObject(new TraceHeap(5));
cout << pMyObject->getVal() << endl;
P* a = new P(5);
delete a;
//system("pause");
}
上面的两个类中P类只有在调用delet时,才会释放器内存,而使用auto_ptr 创建的TraceHeap类指针会自动将其delete。(当然,在使用auto_prt的时候要包含memory库
标准异常
C++自己定义了一些异常类,当这些异常类不能满足自己的需求时,库自己构建其派生类,在使用标准异常时要包含<stdexcept>这头文件
#include<iostream>
#include<stdexcept>
using namespace std;
class MyError : public runtime_error{
public:
MyError(const string& msg=" "):runtime_error(msg){
}
};
int main(int argc,char** argv){
try
{
throw MyError("my message");
}
catch(MyError& e)
{
cerr << e.what() << endl;
}
system("pause");
}
自定义异常类就是标准类runtime_error的派生类
bad_alloc:内存分配异常,一般出现子啊new一个空间不够大的情况
int main()
{
try{
int* ptr = new int [0x1ffffffff];
}catch(bad_alloc)
{
cout<<"out of memory"<<endl;
}
在处理new申请内存时,可以使用new(nothrow)来进行
当申请不到内存时,或者内存不够分配时则会返回NULL,可以避免使用try语句来进行异常监控
int* ptr = new(nothrow) int [0x1ffffffff];
if(!ptr)
{
cout<<"Bad_alloc"<<endl;
}
使用异常的规则
- 不要在异步进程中使用异常
: 因为c++中的异步操作是发生在流程之外的。异常要和其处理器要同一个函数调用栈上,也就是说,异常依赖于程序上动态函数调用栈,而异步事件必须有完全独立的代码来实现,这些代码不是正常流程的一部分。 - 不要在简单处理中使用异常
: 如果有足够多的信息来接受和处理发生的错误,就尽量不要使用异常处理机制,将其抛出到上一层语境中。 - 不要将异常用于流程控制
: 其的操作方式很像switch语句,但是其处理流程的控制差很多 - 不要在析构函数中抛出异常
:因为在异常处理过程中就会调用对象的析构函数,如果抛出了异常,就有可能析构函数的调用会在异常之前调用,这样就会触发新的异常,就会掉哦那个teminate()函数。如果真的在析构函数中调用函数会发生异常,就使用try语句块,在其内部就将异常进行处理掉即可。