1.异常抛出
C++用throw抛出一个异常对象,即throw语句后的是一个对象,可以是一个基本类型的对象也可以是一个用户自定义的对象,一般要为抛出的异常创建一个特定的类。C++中的标准异常类为exception,在<exception>中定义,它是任何异常类的父类,其中runtime_error和logic_error是exception的两个主要的派生类,它们都有一个含有字符串的构造函数,而exception则没有,所以通常用户的自定义类可以继承runtime_error。一般情况下,程序抛出异常的地方和异常被处理的地方相差较远。
class MyExcept
{
const char *msg;
public:
MyExcept(const char *m):msg(m){}
};
void f() throw (MyExcept);
void f()
{
throw MyExcept("MyExcept");
}
2.异常函数的声明
void f() throw (toobig,toosmall);//标明所有可能抛出的异常类型
void f(); //函数可能抛出任何类型的异常
void f() throw ();//函数不抛出任何类型的异常
3.异常匹配和捕获
同Java一样,C++用catch捕获抛出的异常,C++匹配异常的方式是从catch列表中找到和异常类型最为相似的,然后停止向后匹配,其中子类可以匹配其父类,所以一般要将父类型的异常写在catch列表的后面,且尤其要注意的是,如果catch语句中的是异常父类对象的话,则派生类会被切割,所以派生类中的一些相关信息会丢失,因此,catch语句中一般为异常对象的引用或指针,异常对象中的what()方法会打印出相关的异常信息。
class MyError:public runtime_error
{
public:
MyError(const char *msg):runtime_error(msg){}
};
void f()
{
throw MyError(“exception happened.”);
}
int main()
{
try
{
f();
}
catch(MyError& exp)
{
cout<<exp.what()<<endl;
}
}
同其他语言一样,如果某个异常被抛出后,得不到处理的话,那么它就会被传到上一层,一直到最后如果仍然得不到处理,则调用terminate()函数,terminate()会调用标准C函数库中的abort()函数,而当abort()被调用时,程序不会调用正常的终止函数,此时,全局对象和静态对象的析构函数不能被调用,因此会造成内存泄露。此外,当析构函数抛出异常时,terminate()函数也会被调用,因此,一般来说,不允许析构函数抛出异常。
4.清理
C++的异常处理确保在程序的执行流程离开某个作用域的时候,对于同一个作用域里由构造函数建立起来的对象,它的析构函数必须被调用。有一种情况,即在catch语句中,又抛出了异常,如果catch中有释放空间的代码,那么就有可能会没有释放一些堆上动态生成的对象。此时可以用auto_ptr来帮助清理。
5.函数级try块
因为构造函数可以抛出异常,因此有时希望在派生类的成员还没有初始化的时候能够处理父类构造函数抛出的异常,此时可以用函数级try块。函数级try块的具体写法为try写在函数的开始花括号前面,catch写在函数的结束花括号后面。对于成员初始化列表,try要写在初始化列表前面。
#include <iostream>
#include <exception>
using namespace std;
class Base
{
int i;
public:
class BaseExcept{};
Base(int i):i(i){throw BaseExcept();}
};
class Derived:public Base
{
public:
class DerivedExpt:public runtime_error
{
public:
DerivedExpt(const char *msg):runtime_error(msg){}
};
Derived(int i) try:Base(i){}
catch(...){throw DerivedExpt("exception happened in constructor.");}
};
int main()
{
Derived derived(2);
return 0;
}