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)
===================================================================================