C++异常处理:
C++的异常处理机制是用于将运行时错误检测和错误处理功能分离的一种机制(符合高内聚低耦合的软件工程设计要求), 这里主要总结一下C++异常处理的基础知识, 包括基本的如何引发异常(使用throw)和捕获异常(try catch)相关使用注意点, 以及C++标准库提供的一套标准异常类和这些异常类的继承层级结构以及相关使用方法和常用习惯.
throw
: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
catch
: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
try
: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:
try
{
// 保护代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}
如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。
void Func()
{
try
{
// 这里的程序代码完成真正复杂的计算工作,这些代码在执行过程中
// 有可能抛出DataType1、DataType2和DataType3类型的异常对象。
}
catch(DataType1& d1)
{
}
catch(DataType2& d2)
{
}
catch(DataType3& d3)
{
}
catch(…)
{
}
}
注意上面try block中可能抛出的DataType1
、DataType2
和DataType3
三种类型的异常对象在前面都已经有对应的catch block来处理。但为什么还要在最后再定义一个catch(…) block呢?这就是为了有更好的安全性和可靠性,避免上面的try block抛出了其它未考虑到的异常对象时导致的程序出现意外崩溃的严重后果。
抛出异常
引发C++异常的语法就是使用throw语句:throw object
;注意这里throw抛出的是一个对象,也就是说是一个实例。一旦抛出,发生两件事情:第一,C++异常机制开始寻找try catch模块,寻找和抛出的对象的类型相匹配的catch子句找到处理代码进行异常的处理,这个过程是一个栈展开的过程,也就是说C++讲先从当前的函数体里面寻找try catch模块,如果没有,则在调用当前函数(比如我们叫当前函数A)的函数(我们叫调用A的函数B)寻找处理代码(在B里面寻找),一直寻找直到找到匹配的catch子句,然后运行catch里面的代码,运行完毕以后,从这个匹配的catch后面的代码继续运行。第二,栈展开前面的所有函数作用域都失效(比如, A调用B,B调用C,C调用D,D调用E,E抛出异常同时在C找到了处理异常的catch子句,那么D,E作用域失效,等效于D,E运行到了函数结尾),局部对象(自动释放内存的对象,而不是那些动态分配内存的对象,这一点和异常安全都将调用析构函数进行销毁。throw是一个C++关键字,与其后的操作数构成了throw语句,语法上类似于return语句,您可以使用throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。以下是尝试除以零时抛出异常的实例:
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
捕获异常
如果要试图捕获C++异常, 那么将可能抛出(throw)异常的代码块放到try{}里面, 在try{} 后面跟上catch(exception e) {}, 这里的e是一般的异常对象, C++异常处理通过抛出对象的类型来判断决定激活哪个catch处理代码. 具体语法可以参见任何一本C++的书籍. 这里主要提几点注意点:
1. 讲throw的时候也提到了, catch是一层一层catch(栈展开), 当寻找到main里面也没有catch捕获的时候, C++机制一般将调用terminate终止进程(abort)。
2. catch子句列表中, 最特殊的catch必须最先出现, 不然永远都不可能执行到。
3. catch(…) 这个语法表示catch捕获所有异常。
4. 在catch里面使用throw ;这条语句将重新抛出异常对象, 改异常对象是和捕获的一场对象同一个对象(catch中可以修改这个对象)。
try
{
// 保护代码
}catch( ExceptionName e )
{
// 处理 ExceptionName 异常的代码
}
上面的代码会捕获一个类型为 ExceptionName
的异常。如果您想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 …,如下所示:
try
{
// 保护代码
}catch(...)
{
// 能处理任何异常的代码
}
下面是一个实例,抛出一个除以零的异常,并在 catch 块中捕获该异常。
实例:
#include <iostream>
using namespace std;
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
int main ()
{
int x = 50;
int y = 0;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
}catch (const char* msg) {
cerr << msg << endl;
}
return 0;
}
由于我们抛出了一个类型为 const char*
的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*
。当上面的代码被编译和执行时,它会产生下列结果:
Division by zero condition!
C++ 标准的异常
C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
下表是对上面层次结构中出现的每个异常的说明:
定义新的异常
您可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:
实例
#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)
{
//其他的错误
}
}
这将产生以下结果:
MyException caught
C++ Exception
在这里,what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。
相关链接
C++异常处理解析1: 异常的引发(throw), 捕获(try catch), 标准异常等
C++异常处理解析2: 异常安全(内存泄露, 空指针等问题)
C++异常处理解析3: 错误处理(返回值, 错误标志变量, 异常)各有千秋