Fork me on GitHub

C++异常之四 异常类型的生命周期

异常类型的生命周期

 

1. throw 基本类型:

int、float、char

这三种类型的抛出和函数的返回传值类似,为参数拷贝的值传递。

 1 int test_1(int num) throw (int, double, char)
 2 {
 3     if (num == 0)
 4     {
 5         throw -1;
 6     }else if (num == 1){
 7         throw 0.01;
 8     }else{
 9         throw 'A';
10     }
11 
12     return 0;
13 }
14 
15 int main()
16 {
17     int ret = 0;
18     try
19     {
20         ret = test_1(2);
21     }
22     catch (int error) {
23         printf("捕捉到 int 类型异常:%d", error);
24     }catch (double error) {
25         printf("捕捉到 double 类型异常:%f", error);
26     }catch (char error) {
27         printf("捕捉到 char 类型异常:%c", error);
28     }
29 
30     return 0;
31 }

 

 

2 throw 字符串类型:

string、char[num]

与普通变量区别不大,字符串抛出的是字符串的地址,如下边的代码:

test() 传1 过去运行结果:

抛出 char * 字符串异常地址为:0097CDB8
char* error = (char*)被捕获异常地址为:0097CDB8

test() 传2 过去运行结果:

抛出 string 字符串异常地址为:00CE5F18
string *error = new string被捕获异常地址为:00CE5F18

抛出字符串与普通变量也没有多大区别,也是值传递,但传的是指针,而且修饰指针的 const 也要严格进行类型匹配。

 1 int test(int num) throw (char *, string)
 2 {
 3     if(num == 1)
 4     {
 5         const char* error = "char* error = (char*)";            //修饰指针的 const 也要严格进行类型匹配。
 6         printf("抛出 char * 字符串异常地址为:%p\n", error);
 7         throw error;
 8     }
 9     else if (num == 2) 
10     {
11         string *error = new string("string *error = new string");
12         printf("抛出 string 字符串异常地址为:%p\n", error);
13         throw error;
14     }
15 
16     return 0;
17 }
18 
19 int main()
20 {
21     int ret = 0;
22     try
23     {
24         ret = test(1);
25     }
26     catch (const char *error)
27     {
28         printf("%s被捕获", error);        
29         printf("异常地址为:%p\n", error);
30     }
31     catch (string *error)
32     {
33         printf("%s被捕获", error->c_str());
34         printf("异常地址为:%p\n", error);
35         //别忘记释放内存
36         delete error;
37     }
38 
39     return 0;
40 }

 

 

3 throw 类类型异常:

  首先要说明,throw 类类型的最佳方式是:抛出类的匿名对象,使用引用类型捕捉对象,这种方法效率最高,下边将会具体说明。

   throw 类类型的生存周期分几种情况,生存周期的不同与编译器对于类的处理有关,用下方的代码举例:

 1 #include <iostream>
 2 #include <string>
 3 
 4 class ErrorException
 5 {
 6 public:
 7     ErrorException();
 8     ~ErrorException();
 9     ErrorException(const ErrorException& error);
10 
11 private:
12     int ID = -1;
13 };
14 
15 ErrorException::ErrorException()
16 {
17     ID = 0;
18     printf("ErrorException 构造\n");
19 }
20 
21 ErrorException ::~ErrorException()
22 {
23     printf("ErrorException ~析构 (ID:%d)\n", ID);
24 }
25 
26 ErrorException::ErrorException(const ErrorException& error)
27 {
28     ID = 1;
29     printf("ErrorException 拷贝构造函数\n");
30 }
31 
32 int test(int num) throw (ErrorException)
33 {
34     if (num == 1)
35     {
36         throw ErrorException();      //这里抛出的是匿名对象
37     }
38 
39     return 0;
40 }
41 
42 int main()
43 {
44     int ret = 0;
45     try
46     {
47         ret = test(1);
48     }
49     catch (ErrorException &error)
50     {
51         printf("捕捉到异常!ErrorException 类型\n");
52     }
53     catch (...)
54     {
55         printf("没有捕捉到异常类型\n");
56     }
57 
58     return 0;
59 }

===================================================================================
第49行如果 error 没有引用,运行结果:

ErrorException 构造
ErrorException 拷贝构造函数
捕捉到异常!ErrorException 类型
ErrorException ~析构 (ID:1)
ErrorException ~析构 (ID:0)

说明:调用了两次析构函数,一次是在 36行创建了匿名对象抛出,一次是在49行,因为使用了形参值传递,会进行两个对象的创建。

从ID 的变化可以看出,先析构拷贝构造函数,后析构构造函数。


===================================================================================
第49行如果 error 使用了引用,运行结果:

ErrorException 构造
捕捉到异常!ErrorException 类型
ErrorException ~析构 (ID:0)

说明:使用引用的话,将会少创建一个对象,是36行的临时匿名对象的地址进行抛出,捕捉时直接捕捉引用,不用再次创建,这种效率最高。


===================================================================================
第36行,如果先创建对象,然后37行 throw 对象,50行用形参接收,则会变得更加低效,这一切要说下编译器在背地里做的很多事情,如下代码:

 1 /* . . . 省略 . . . */
 2 int test(int num) throw (ErrorException)
 3 {
 4     if (num == 1)
 5     {
 6         ErrorException error;          //进行对象创建
 7         throw error;               //进行对象抛出
 8     }
 9 
10 /* . . . 省略 . . . */
11     try
12     {
13         ret = test(1);
14     }
15     catch (ErrorException error)        //使用形参接收
16     {
17         printf("捕捉到异常!ErrorException 类型\n");
18     }

运行结果:

ErrorException 构造
ErrorException 拷贝构造函数 
ErrorException 拷贝构造函数
ErrorException ~析构 (ID:0)
捕捉到异常!ErrorException 类型
ErrorException ~析构 (ID:1)
ErrorException ~析构 (ID:1)

说明:

ErrorException 构造         ------------> 36 行创建对象
ErrorException 拷贝构造函数      -----------> 由于 36 行创建的对象作用域在函数内部,但异常要继续往外抛,编译器会创建一个临时的匿名对象
ErrorException 拷贝构造函数     ------------> 第50行创建的error,用上边生成的临时的匿名对象进行拷贝。
ErrorException ~析构 (ID:0)     ------------> 由于 test 函数结束,36行创建的对象被析构。
捕捉到异常!ErrorException 类型    -----------> 进入到 52 行打印。
ErrorException ~析构 (ID:1)     ------------> 第50行创建的error对象,走出作用域销毁。
ErrorException ~析构 (ID:1)     ------------> 离开第50行的 catch 的作用域时 临时的匿名对象被销毁。

 

===================================================================================

   如果使用指针进行异常抛出,大同小异,用上边的代码举例,要注意的就是内存的释放,如下代码:

 1 /* . . . 省略 . . . */
 2 int test(int num) throw (ErrorException)
 3 {
 4     if (num == 1)
 5     {
 6         throw new ErrorException();                    //动态内存分配
 7     }
 8 
 9 /* . . . 省略 . . . */
10     catch (ErrorException *error)
11     {
12         printf("捕捉到异常!ErrorException 类型\n");
13         delete error;                            //注意释放内存
14     }

  运行结果:

ErrorException 构造
捕捉到异常!ErrorException 类型
ErrorException ~析构 (ID:0)

   ===================================================================================

 

 

1. 异常处理的三个关键字

点击查看

2. 异常处理的基本语法

点击查看

3.异常处理接口声明

点击查看

4.异常类型的生命周期

4.1 throw 基本类型:

点击查看

4.2 throw 字符串类型:

点击查看

4.3 throw 类类型异常:

点击查看

5.异常和继承

点击查看

6.异常处理的基本思想

点击查看 

7.标准库里的异常类

点击查看

posted @ 2020-03-20 00:54  索智源  阅读(1027)  评论(0编辑  收藏  举报