异常类
除了抛出内置类型的异常值之外,在C++中,异常还可以用类来实现,以便提供更多的异常信息。接下来通过一个案例来学习异常类的定义及使用,本例将例7-4中出现除零错误时抛出的异常描述为一个类对象,如例1所示。
例1
1 #include <iostream>
2 using namespace std;
3
4 class ZeroException{ //定义描述除零异常的类
5 public:
6 ZeroException():msg("div zero!"){} //构造函数
7 const char *getmsg() { return msg; } //获取msg信息
8 private:
9 const char *msg;
10 };
11 int int_div(int n1, int n2)
12 {
13 if (n2 == 0){
14 throw ZeroException(); //抛出异常类对象
15 }
16 return n1 / n2;
17 }
18
19 int main()
20 {
21 int int_n1, int_n2; //定义两个整型变量
22
23 while (1){ //循环多次读取数据,完成相除操作
24 cout << "Please input two integers:"; //输入提示
25 cin >> int_n1 >> int_n2; //输入两个整数
26 try {
27 cout << "Maybe exception code:" << endl;
28 cout << int_n1 << "/" << int_n2 << " = "
29 << int_div(int_n1, int_n2) << endl;//除数非零显示相除结果
30 cout << "in try, after div!" << endl;
31 }
32 catch(ZeroException divzero) //捕捉异常
33 {
34 cout << "exception:"
35 << divzero.getmsg() << endl; //异常处理代码
36 }
37 }
38 return 0;
39 }
若除数是0,程序运行结果如图1所示。
图1 例1运行结果
例1运行结果与前面例子给出的结果类似,只是本例中定义了类ZeroException,用来描述除零异常时的信息。在函数int_div()中若除数为零则抛出ZeroException类异常。
上例中,抛出异常时定义了类对象,若除了抛出的异常类对象外,在try块中定义了一个普通的、非异常对象,由于try块内定义的局部变量在try块外不可使用,而且对于抛出异常的动作而言,抛出异常后一般采取终止模式,因此若try结构中定义过类对象,应能够自动调用析构函数完成自动清理工作,C++的异常处理机制具有为抛出异常前构造的所有局部对象自动调用析构函数的能力。
接下来在例7-5中增加类Example,并在int_div()函数中定义Example类对象,观察抛出、处理异常后对局部对象的资源回收操作。另外,通常若抛出的是类异常,则catch结构使用类引用接收异常信息。具体代码如例2所示。
例2
1 #include <iostream>
2 using namespace std;
3
4 class Example{ //定义Example类
5 public:
6 Example(){ cout << "Example constructor!" << endl; }
7 ~Example() { cout << "Example destructor!" << endl; }
8 };
9 class ZeroException{ //定义描述除零异常的类
10 public:
11 ZeroException();
12 ~ZeroException();
13 ZeroException(ZeroException &ref);
14 const char *getmsg() { return msg; }
15 private:
16 const char *msg;
17
18 };
19 ZeroException::ZeroException():msg("div zero") //ZeroException类构造函数
20 {
21 cout << "ZeroException constructor!" << endl;
22 }
23 ZeroException::ZeroException(ZeroException &ref) //ZeroException类拷贝构造函数
24 {
25 msg = ref.msg;
26 cout << "ZeroException cp constructor!" << endl;
27 }
28 ZeroException::~ZeroException() //ZeroException类析构函数
29 {
30 cout << "ZeroException destructor!" << endl;
31 }
32
33 int int_div(int n1, int n2) //整数相除函数
34 {
35 //定义Example类对象obj,该对象在抛出异常时会被析构
36 Example obj;
37 if (n2 == 0){
38 throw ZeroException();
39 }
40 return n1 / n2;
41 }
42
43 int main()
44 {
45 int int_n1, int_n2; //定义两个整型变量
46
47 while (1){ //循环多次读取数据,完成相除操作
48 cout << "Please input two integers:"; //输入提示
49 cin >> int_n1 >> int_n2; //输入两个整型数
50 try{
51 cout << "Maybe exception code:" << endl; //提示可能出现异常的代码信息
52 cout << int_n1 << "/" << int_n2 << " = "
53 << int_div(int_n1, int_n2) << endl; //除数非零显示相除结果
54 cout << "in try, after div!" << endl;
55 }
56 catch(ZeroException &divzero) //捕捉ZeroException类的异常
57 //catch(ZeroException divzero) //捕捉ZeroException类的异常
58 {
59 cout << "exception:"
60 << divzero.getmsg() << endl; //异常处理代码
61 }
62 }
63 return 0;
64 }
若除数为0,程序运行结果如图2所示。
图2 例2运行结果
从例2运行结果中可以看出,int_div()函数中定义了两个类对象,一个是函数中局部对象Example类对象obj,另一个是抛出异常时定义的ZeroException类对象。
对于普通对象obj来说,异常处理机制具有自动析构局部对象的能力,从运行结果可以看出,该对象确实在抛出异常前被析构。
异常类对象抛出后,catch块会接收它以便执行相应的处理动作,从运行结果看出,在catch块结束后该对象才被析构。
将第56行代码修改为第57行的内容,catch块中接收异常的类引用改为类对象,程序运行结果如图3所示。
图3 异常类对象接收异常信息
从运行结果看出,若catch块采用异常类对象接收异常信息,则在抛出异常时将通过拷贝构造函数进行对象复制,异常处理完后才将两个异常对象进行析构,释放资源,因此在catch块中通常采用类引用接收异常,防止多次调用构造、析构函数,降低运行效率。