本篇博客主要介绍C++ 中异常处理机制。
概述 C++ 自身有着非常强的纠错能力,发展到如今,已经建立了比较完善的异常处理机制。C++ 的异常情况无非两种,一种是语法错误 ,即程序中出现了错误的语句,函数,结构和类,致使编译程序无法进行。另一种是运行时发生的错误 ,一般与算法有关。关于语法错误,不必多说,写代码时心细一点就可以解决。C++ 编译器的报错机制可以让我们轻松地解决这些错误。
第二种是运行时的错误,常见的有文件打开失败、数组下标溢出、系统内存不足等等。而一旦出现这些问题,引发算法失效、程序运行时无故停止等故障也是常有的。这就要求我们在设计软件算法时要全面。比如针对文件打开失败的情况,保护的方法有很多种,最简单的就是使用“return”命令,告诉上层调用者函数执行失败;另外一种处理策略就是利用c++ 的异常机制,抛出异常。
c++异常处理机制 C++ 异常处理机制是一个用来有效地处理运行错误的非常强大且灵活的工具,它提供了更多的弹性、安全性和稳固性,克服了传统方法所带来的问题.
异常的抛出和处理主要使用了以下三个关键字: try、 throw 、 catch 。
抛出异常即检测是否产生异常,在C++中,其采用throw语句来实现,如果检测到产生异常,则抛出异常。该语句的格式为:
如果在 try 语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被 try 语句块后的某个catch语句所捕获并处理,捕获和处理的条件是被抛弃的异常的类型与catch语句的异常类型相匹配。由于C++使用数据类型来区分不同的异常,因此在判断异常时,throw语句中的表达式的值就没有实际意义,而表达式的类型就特别重要。 try-catch语句形式如下 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 try { 包含可能抛出异常的语句; } catch (类型名 [形参名]) { } catch (类型名 [形参名]) { } catch (...) { }
示例1: 处理除数为0的异常。该范例将上述除数为0的异常可以用try/catch
语句来捕获异常,并使用throw
语句来抛出异常,从而实现异常处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream.h> #include <stdlib.h> double fuc (double x, double y) { if (y==0 ) { throw y; } return x/y; } void main () { double res; try { res=fuc (2 ,3 ); cout<<"The result of x/y is : " <<res<<endl; res=fuc (4 ,0 ); 出现异常,函数内部会抛出异常 } catch (double ) { cerr<<"error of dividing zero.\n" ; exit (1 ); } }
示例二: 自定义异常类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 #include "stdafx.h" #include <stdlib.h> #include <crtdbg.h> #include <iostream> #define _CRTDBG_MAP_ALLOC #ifdef _DEBUG #define new new(_NORMAL_BLOCK, __FILE__, __LINE__) #endif class MyExcepction { public : MyExcepction (int errorId) { std::cout << "MyExcepction is called" << std::endl; m_errorId = errorId; } MyExcepction ( MyExcepction& myExp) { std::cout << "copy construct is called" << std::endl; this ->m_errorId = myExp.m_errorId; } ~MyExcepction () { std::cout << "~MyExcepction is called" << std::endl; } int getErrorId () { return m_errorId; } private : int m_errorId; }; int main (int argc, char * argv[]) { _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); int throwErrorCode = 110 ; std::cout << " input test code :" << std::endl; std::cin >> throwErrorCode; try { if ( throwErrorCode == 110 ) { MyExcepction myStru (110 ) ; throw &myStru; } else if ( throwErrorCode == 119 ) { MyExcepction myStru (119 ); throw myStru; } else if ( throwErrorCode == 120 ) { MyExcepction * pMyStru = new MyExcepction (120 ); throw pMyStru; } else { throw MyExcepction (throwErrorCode); } } catch ( MyExcepction* pMyExcepction) { std::cout << "执行了 catch( MyExcepction* pMyExcepction) " << std::endl; std::cout << "error Code : " << pMyExcepction->getErrorId ()<< std::endl; } catch ( MyExcepction myExcepction) { std::cout << "执行了 catch ( MyExcepction myExcepction) " << std::endl; std::cout << "error Code : " << myExcepction.getErrorId ()<< std::endl; } catch (...) { std::cout << "执行了 catch(...) " << std::endl; throw ; } int temp; std::cin >> temp; return 0 ; }
C++ 标准异常 C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
下表是对上面层次结构中出现的每个异常的说明:
异常
描述
std::exception
该异常是所有标准 C++ 异常的父类。
std::bad_alloc
该异常可以通过 new 抛出。
std::bad_cast
该异常可以通过 dynamic_cast 抛出。
std::bad_exception
这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid
该异常可以通过 typeid 抛出。
std::logic_error
理论上可以通过读取代码来检测到的异常。
std::domain_error
当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument
当使用了无效的参数时,会抛出该异常。
std::length_error
当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range
该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator 。
std::runtime_error
理论上不可以通过读取代码来检测到的异常。
std::overflow_error
当发生数学上溢时,会抛出该异常。
std::range_error
当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error
当发生数学下溢时,会抛出该异常。
定义新的异常 您可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <exception> using namespace std;struct MyException : public exception{ const char * what () const throw () { return "C++ Exception" ; } }; int main () { try { throw MyException (); } catch (MyException& e) { std::cout << "MyException caught" << std::endl; std::cout << e.what () << std::endl; } catch (std::exception& e) { } }
这将产生以下结果:
1 2 MyException caught C++ Exception
在这里,what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。
异常的接口声明 为了加强程序的可读性,使函数的用户能够方便地知道所使用的函数会抛出哪些异常,可以在函数的声明中列出这个函数可能抛出的所有异常类型,例如:
1 void fun () throw ( A,B,C,D) ;
这表明函数fun()可能并且只可能抛出类型(A,B,C,D)及其子类型的异常。如果在函数的声明中没有包括异常的接口声明,则此函数可以抛出任何类型的异常,例如:
一个不会抛出任何类型异常的函数可以进行如下形式的声明:
异常处理中需要注意的问题
如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止
一般在异常抛出后资源可以正常被释放,但注意如果在类的构造函数中抛出异常,系统是不会调用它的析构函数的,处理方法是:如果在构造函数中要抛出异常,则在抛出前要记得删除申请的资源。
异常处理仅仅通过类型而不是通过值来匹配的,所以catch块的参数可以没有参数名称,只需要参数类型。
函数原型中的异常说明要与实现中的异常说明一致,否则容易引起异常冲突。
应该在throw语句后写上异常对象时,throw先通过Copy构造函数构造一个新对象,再把该新对象传递给 catch.
那么当异常抛出后新对象如何释放? 异常处理机制保证:异常抛出的新对象并非创建在函数栈上,而是创建在专用的异常栈 上,因此它才可以跨接多个函数而传递到上层,否则在栈清空的过程中就会被销毁。所有从try到throw语句之间构造起来的对象的析构函数将被自动调用。但如果一直上溯到main函数后还没有找到匹配的catch块,那么系统调用terminate()
终止整个程序,这种情况下不能保证所有局部对象会被正确地销毁。
catch块的参数推荐采用地址传递 而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常扑获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获。
编写异常说明时,要确保派生类成员函数的异常说明和基类成员函数的异常说明一致,即派生类改写的虚函数的异常说明至少要和对应的基类虚函数的异常说明相同,甚至更加严格,更特殊。
最后更新时间:2022-09-01 20:59:40
欢迎交流,转载请注明文章来源!