计算机应用程序中离不开错误处理,尤其是生产型大型软件系统。应用软件系统运行属于循环处理事务,出错后需要保证不能让软件程序直接退出。这就需要使用一定的程序容错处理来应对。一般情况下,大型软件开发中的软件系统容错处理会结合异常处理、错误代码定义的使用与相应的出错处理日志记录,包括一定的参与大型生产系统的监控系统等配合保障系统的稳定性。下面本章将会就C++软件系统中提供的异常处理作详细的讲述,包括基本概念以及应用操作情况。
14.1 C++异常处理简介
软件应用程序中,异常处理机制是一种比较有效的处理系统运行时错误的方法。C++针对异常处理提供了一种标准的方法,用于处理软件程序运行时的错误,并用于处理软件系统中可预知或不可预知的问题。这样就可以保证软件系统运行的稳定性与健壮性。
C++中异常的处理主要用于针对程序在运行时刻出现错误而提供的语言层面的保护机制。它允许开发者最大限度地发挥,针对异常处理进行相应的修改调整。
C++应用程序中在考虑异常设计时,并不是所有的程序模块处理都需要附加上异常情况的处理。相对来说,异常处理没有普通方法函数调用速度快。过度的错误处理会影响应用程序运行的效率。通常在C++开发的软件系统中,应用程序都由对应的库、组件以及运行的具体不同模块组成。在设计时,异常的处理应充分考虑到独立程序库以及组件之间的情况。便于使用者在程序出现异常情况下,使用库或者组件的开发者能够快速定位出库、组件还是应用程序的错误。
由于大型软件系统中库与组件通常都是以独立的程序方式提供给具体设计开发模块人员。开发人员通常不了解组件与库中具体实现情况,只是根据提供的接口来操作使用。在不清楚具体库与组件的具体实现情况下,开发者可能在极端的情况下操作应用库或者组件提供的方法。此时相应的异常处理通信就会成为必不可少的错误处理通信机制。下面将会就C++语言提供的异常机制支持作详细应用讲述,在实践中总结异常应用的场合与具体实现方法。
14.2 C++异常处理方法
C++语言异常部分的支持也是在后续语言发展中逐步讨论并添加的内容。该语言针对异常处理提供了一系列的语法支持。C++语言除了提供异常的关键字语法支持以外,其标准库中支持异常处理而封装异常类也很好的为应用程序中异常处理判断使用提供直接的帮助。
14.2.1 C++异常处理:try、throw与catch
C++语言中针对异常处理提供了三个关键字,分别为try、throw与catch。C++应用程序中通过这三个关键字实现机制组合来实现异常的处理。下面通过常见异常处理操作方式来介绍应用程序中使用异常处理关键字的基本方式,其基本语法定义如下所示。
try
{
… //可能出错产生异常的代码
throwtypen();
}catch(type1)
{
… //对应类型的异常处理代码
}catch(type2)
{
… //对应类型的异常处理代码
}
… //更多类型异常处理代码
或者
try
{
function();//调用可能会抛出异常的函数方法
}catch(type1)
{
… //对应类型的异常处理代码
}catch(type2)
{
… //对应类型的异常处理代码
}
…
C++应用程序中,try关键字后的代码块中通常放入可能出现异常的代码。随后的catch块则可以是一个或者多个;catch块主要用于异常对应类型的处理。try块中代码出现异常可能会对应多种异常处理情况,catch关键字后的圆括号中则包含着对应类型的参数。
try块中代码体作为应用程序遵循正常流程执行。一旦该代码体中出现异常操作,会根据操作的判断抛出对应的异常类型。随后逐步的遍历catch代码块,此步骤与switch控制结构有点相像。当遍历到对应类型catch块时,代码会跳转到对应的异常处理中执行。如果try块中代码没有抛出异常,则程序继续执行下去。
try体中可以直接抛出异常,或者在try体中调用的函数体中间接的抛出。下面将会通过一个简单的异常处理实例,来使用C++语言中支持的异常处理方式。
1.准备实例
打开UE工具,创建新的空文件,并且另存为chapter1401.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1401
* 源文件chapter1401.cpp
* 简单异常处理
*/
#include <iostream>
#include <string>
using namespace std;
int main()
{
try
{
intvalue1,value2; //定义两个整型变量
cout<<"Pleaseinput two value:"<<endl; //提示输入信息
cin>>value1>>value2; //从键盘输入两个整型数
cout<<"Maybeexception code:"<<endl; //提示可能出现异常的代码信息
if(value2== 0) //如果除数为0则抛出异常
{
throw0;
}
else //否则直接计算相除操作
{
cout<<"value1/value2:"<<(value1/value2)<<endl;
}
}catch(inti) //捕捉参数为整型的异常
{
cout<<"divisoris 0!"<<endl; //异常处理代码
}
return0;
}
本实例程序主要通过try…catch方式来捕获异常,演示简单容错处理的一般实现方法。程序主要在main()函数内部定义实现,具体程序剖析见程序注释与后面讲解。
2.编辑makefile
Linux平台下需要编译源文件为chapter1401.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1401.o
CC=g++
chapter1401: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1401
clean:
rm-f chapter1401 core $(OBJECTS)
submit:
cp-f -r chapter1401 ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件。随后,通过make submit命令提交程序文件至本实例bin目录。通过cd命令定位至实例bin目录,执行该程序文件运行,结果如下所示。
[developer@localhost src]$ make
g++ -c-o chapter1401.o chapter1401.cpp
g++ chapter1401.o -g -o chapter1401
[developer @localhost src]$ make submit
cp -f -r chapter1401 ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./chapter1401
Please input two value:
12
0
Maybe exception code:
divisor is 0!
本实例中主要演示了除法中除数为0时抛出相应的异常,并作出对应的处理。本实例中try块中主要放置着可能出现异常的代码。该代码块中首先定义两个整型变量value1与value2。随后提示从键盘输入两个整型数,接着输出可能出现异常的提示信息。最后判断输入的除数value2是否为0。如果该判断条件成立,即程序执行中发生了除数为0的错误,则对应的代码体中使用throw抛出异常。如果判断条件不成立,则程序继续执行下去,
一旦符合抛出异常条件成立,则执行相应代码采用throw关键字抛出异常。此时关键字后跟着一个整型数0表示该异常。随后catch语句块捕捉异常,由于catch随后的参数类型符合抛出异常的类型匹配则执行catch中的异常处理,即打印输出除数为0的提示信息。在主程序中try块中抛出的异常的类型需要与随后的catch中参数类型一致,否则会程序将会异常终止。初学者可以将上述catch中参数类型修改为double类型测试看看当前编译器针对异常发生后匹配不到对应的catch时给出的反应。
如果try块中的代码没有抛出异常,则程序会直接忽略随后的catch块,直接执行后续的代码处理。在设计程序可能出现的异常处理时,将抛出异常的类型与对应的catch块中的类型规范的对应起来有助于程序在异常处理加入后更加清晰有条理。
14.2.2 throw抛出异常处理
C++程序中异常抛出是采用关键字throw实现的。通常throw关键字后会跟随着一个操作数,该操作数可以是一个表达式、一个C++内置类型数据或者为类类型的对象等。最常见的异常抛出都会放在try代码块中。当然,C++也允许抛出异常的地方在函数中供try块中的代码调用。正常情况下异常抛出点的定义不要放在try块外部无关的地方,因为那样通常会引起编译器默认的终止程序处理。最好的情况下,异常抛出点直接或者间接的定义在try块中。
try块中可以包含一个或者多个异常抛出点。但是需要注意的是,异常只要一抛出,对应的catch块捕捉到后,该try块中以下的代码体执行会被终止。代码执行直接进入对应的catch块中,最后catch块执行处理完异常后直接跳转至所有当前try块对应的catch块之后。
下面通过一个具体的实例来演示throw异常抛出的基本操作与需要注意的使用情况。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1402.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1402
* 源文件chapter1402.cpp
* 简单异常处理
*/
#include <iostream>
#include <string>
using namespace std;
void divideCompute(int value1,int value2) //除法计算函数定义
{
if(value1== 0) //如果被除数为0,则抛出参数类型为浮点型异常
{
throw0.0;
}
elseif(value2 == 0) //如果除数为0,则抛出参数类型为整型异常
{
throw0;
}
else //否则执行除法运算并打印输出结果
{
cout<<"value1/value2:"<<(value1/value2)<<endl;
}
}
int main()
{
try
{
intvalue1,value2; //定义两个整型变量
cout<<"Pleaseinput two value:"<<endl; //提示输入两个整型数信息
cin>>value1>>value2; //输入两个整型数
cout<<"Maybeexception code:"<<endl;
divideCompute(value1,value2); //调用除法计算函数,传入两个输入的整型参数
}catch(inti) //捕捉整型参数异常
{
cout<<"divisoris 0!"<<endl; //对应的异常处理
}catch(doublei) //捕捉浮点型参数异常
{
cout<<"dividendis 0!"<<endl; //对应的异常处理
}
cout<<"here!"<<endl; //异常处理块后的正常代码
return0;
}
本实例通过throw关键字演示在函数内部抛出异常,程序调用处捕获异常并处理的场景。程序主要由函数divideCompute与主函数main组成,具体程序剖析见程序注释与后面讲解。
2.编辑makefile
Linux平台下需要编译源文件为chapter1402.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1402.o
CC=g++
chapter1402: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1402
clean:
rm-f chapter1402 core $(OBJECTS)
submit:
cp-f -r chapter1402 ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件。随后通过make submit命令提交程序文件至本实例bin目录。通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer@localhost src]$ make
g++ -c-o chapter1402.o chapter1402.cpp
g++ chapter1402.o -g -o chapter1402
[developer @localhost src]$ make submit
cp -f -r chapter1402 ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./chapter1402
Please input two value:
0
0
Maybe exception code:
dividend is 0!
here!
本实例中主要设计了除法中可能出现的异常。此处抛出异常没有直接放在try代码块中,而是通过间接的方式放置在函数定义中,供try代码块中调用。此时除法操作定义的函数中包含着可能出现的两个异常抛出点。一类是针对被除数为0时,抛出浮点型参数异常。另一类则针对除数为0时,抛出整型参数异常。
整个主程序代码中catch主要有两个参数类型的捕获处理。分别对应着除数以及被除数为0时异常抛出捕获处理。上述实例表明try代码块中可以直接放置或者间接放置多个throw异常抛出点。一旦其中一个异常点抛出后程序控制权将会转移到对应的catch参数类型异常处理中。对应的异常处理完后将会意味着程序执行将会终止try异常块中的程序块。直接转移到对应的所有catch异常处理块之后的代码执行。
上述主程序中从try块中开始执行,try代码快中首先定义两个整型变量value1与value2。随后提示从键盘输入两个整型数,上述程序执行后输入两个整型数都为0。之后将输入的两个整型数作为除法计算函数的实参调用除法计算函数。根据传入的实参首先判断被除数为0则抛出浮点型参数异常,随后程序转移到对应catch块中执行异常处理,输出提示信息“dividend is 0!”。程序代码转移至catch处理块中之后,则退出了异常抛出的所在块。直接处理完毕后,转到所有catch块之后继续执行代码,即最后执行输出“here!”提示信息代码。
14.2.3 catch捕捉异常处理
catch关键字的代码块则主要负责异常的捕获处理,catch块可以拥有多个,并且紧跟着try块之后。每个catch块部分主要由圆括号中参数类型,紧跟其后的大括号中异常代码处理部分构成。前面小节在讲述异常抛出处理时已经对其使用的原理作过解释,即当在可能抛出异常的try代码块中通过throw抛出了异常。随后开始匹配catch块中的参数类型是否符合异常抛出时的类型,如果符合则执行对应catch块中的代码处理。
多个catch块中,遍历到对应的catch块则执行随后代码块处理异常。catch块中随后圆括号中参数类型,该类型可以拥有变量名称或者对象名称,也可以直接是对应的参数类型。如果是对应的类型对象,则在catch块中可以引用该对象处理。而如果是只有数据类型没有相应参数名称的参数,则直接匹配对应的参数类型即可,程序会从try块中转移至catch块中执行。
如果异常抛出了,匹配对应的catch块没有对应类型的异常处理时,则会调用默认的异常abort来终止程序的执行。catch块中的参数除了采用基本的内置类型、自定义类类型外,还可以使用三个点的省略号(即catch(…))来表示捕捉所有的异常。此时,当try块中一旦抛出异常时,如果对应的catch块中只有一个catch(…)或者对应的catch(…)放置在所有其它catch块最后。那么,异常发生时只会执行对应的catch(…)中的代码处理,或者当前面所有的catch都匹配不到时,则匹配catch(…)捕获所有异常处理。通常情况下捕捉所有异常的定义会放在所有其它catch块最后位置,这样的好处就是程序不会因为抛出异常匹配不到对应的catch块而终止。
上述实例代码作出如下修改,根据实例中添加catch(…)块观察捕捉异常处理情况。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1403.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1403
* 源文件chapter1403.cpp
* 捕获所有异常处理
*/
#include <iostream>
#include <string>
using namespace std;
void divideCompute(int value1,int value2) //除法计算函数定义
{
if(value2== 0) //如果除数为0,则抛出相应的整型异常
{
throw0;
}
else //否则计算除法运算,并打印结果
{
cout<<"value1/value2:"<<(value1/value2)<<endl;
}
}
int main()
{
try
{
intvalue1,value2; //定义两个整型变量
cout<<"Pleaseinput two value:"<<endl;
cin>>value1>>value2; //从键盘输入两个整型值
cout<<"Maybeexception code:"<<endl;
divideCompute(value1,value2); //调用除法计算函数
}catch(doublei) //对应浮点型参数异常处理
{
cout<<"doubleexception!"<<endl;
}catch(inti) //对应整型参数异常处理
{
cout<<"divisoris "<<i<<”!”<<endl;
}catch(...) //捕获所有异常处理
{
cout<<"allexception!"<<endl;
}
cout<<"here!"<<endl;
return0;
}
本实例主要通过catch(…)方式使用演示在异常处理相关类型捕获不到的情况下,异常的默认处理情况。程序主要由函数divideCompute()与主函数main()组成,程序具体剖析见程序注释与后面程序讲解。
2.编辑makefile
Linux平台下需要编译源文件为chapter1403.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1403.o
CC=g++
chapter1403: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1403
clean:
rm-f chapter1403 core $(OBJECTS)
submit:
cp-f -r chapter1403 ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer@localhost src]$ make
g++ -c-o chapter1403.o chapter1403.cpp
g++ chapter1403.o -g -o chapter1403
[developer @localhost src]$ make submit
cp -f -r chapter1403 ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./chapter1403
Please input two value:
1
0
Maybe exception code:
divisor is 0!
here!
上述实例中,主程序中根据输入的实参整型数调用除法计算函数。在除法计算函数中,一旦除数为0,则抛出整型参数为0的异常。此时开始匹配try块对应后的catch块中的参数类型。首先对比第一个catch块,由于其参数类型为double类型不符合类型匹配,则继续向下搜索。try块后的第二个捕捉异常处理catch块中的参数类型为整型,并且该参数还拥有参数类型对应变量。匹配到该catch块后,程序转移到对应的代码块中处理,该代码块中主要用于打印输出说明除数为0,此时则直接引用了参数类型后的变量名。
主程序中上述匹配到的catch块执行完之后直接转移至所有catch块之后继续执行。执行最后打印输出语句。此时如果前面几个catch都没有匹配对应的参数类型时,则catch(…)块中异常处理代码会被执行,初学者可以自行修改程序进行测试学习。
14.2.4 throw重抛异常处理
重新抛出异常通常应用在try嵌套层中。最常见的应用即当前try块中捕捉异常块不作任何处理时,可以通过重抛异常让try层外部的catch块来处理。如果try层外部已经没有对应的try与catch块处理,则会调用默认的异常处理终止程序。下面将会通过一个完整实例来演示异常重抛的应用情况。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1404.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1404
* 源文件chapter1404.cpp
* throw重抛异常处理
*/
#include <iostream>
#include <string>
using namespace std;
void divideCompute(int value1,int value2)
{
try
{
if(value2== 0) //如果除数为0,则抛出异常
{
throw0; //抛出整型异常
}
else //否则计算除法运算
{
cout<<"value1/value2:"<<(value1/value2)<<endl;
}
}catch(doublei) //捕捉浮点型参数异常
{
cout<<"doubleexception!"<<endl;
}catch(inti) //捕捉整型参数异常
{
cout<<"rethrowexception:"<<endl;
throw; //重抛异常
}catch(...) //捕捉所有类型异常
{
cout<<"allexception!"<<endl;
}
}
int main()
{
try
{
intvalue1,value2; //定义两个整型变量
cout<<"Pleaseinput two value:"<<endl;
cin>>value1>>value2; //输入两个整型变量
cout<<"Maybeexception code:"<<endl;
divideCompute(value1,value2); //调用除法计算函数
}catch(doublei) //外层捕捉浮点型异常
{
cout<<"doubleexception!"<<endl;
}catch(inti) //外层捕捉整型异常
{
cout<<"divisoris "<<i<<"!"<<endl;
}catch(...) //外层捕捉所有类型异常
{
cout<<"allexception!"<<endl;
}
cout<<"here!"<<endl;
return0;
}
本实例程序主要演示try…catch嵌套使用中,通过throw关键字重新抛出异常的功能。程序主要由函数divideCompute与主函数组成,程序具体剖析见程序注释与后面讲解。
2.编辑makefile
Linux平台下需要编译源文件为chapter1404.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1404.o
CC=g++
chapter1404: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1404
clean:
rm-f chapter1404 core $(OBJECTS)
submit:
cp-f -r chapter1404 ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件。随后通过make submit命令提交程序文件至本实例bin目录。通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer@localhost src]$ make
g++ -c -o chapter1404.o chapter1404.cpp
g++ chapter1404.o -g -o chapter1404
[developer@localhost src]$ make submit
cp -f -r chapter1404 ../bin
cp -f -r *.h ../include
[developer@localhost src]$ cd ../bin
[developer@localhost bin]$ ./chapter1404
Please input two value:
1
0
Maybe exception code:
rethrow exception:
divisor is 0!
here!
本实例中主要演示了try块中调用除法计算方法。该方法中又存在try与catch块的嵌套处理情况。上述实例主程序中同样是定义两个整型变量value1与value2,随后从键盘输入两个整型数作为实参调用除法计算函数。除法计算函数中仍然包含着try块,try块中包含着可能抛出异常的代码。一旦输入的实参中除数为0则报出整型类型的异常,被随后的catch整型参数块所捕获执行相对应的异常处理代码。
在对应的异常处理catch块中允许针对异常不作出任何的处理,而直接使用throw不带任何的参数类型重新抛出异常,交由外层的catch块来捕获。此时代码执行被转移至外层catch块进行匹配。如果匹配到整型类型的异常处理时,则进入执行相应的异常处理。如果没有匹配到则执行catch(…)块捕获所有异常,随后转移至catch块外继续执行。
上述实例程序执行时传入两个整型数1和0。由于除数为0则在除法函数体中try块抛出异常。此时最内一层catch开始进行捕获匹配,匹配到整型参数类型异常处理则进入执行。最内层的catch整型参数中除了打印输出提示重抛异常信息外,则执行throw重抛异常。被外层对应的catch块捕获进入执行相应异常处理,通过直接引用捕获异常参数类型的变量打印除数为0的信息提示。
14.3 C++类体系中异常处理
C++中异常机制同样可以应用于自定义类类型。在C++程序中,大部分的异常处理都是应用于自定义类类型。通常情况下定义处理异常的类,然后再创建一个专门用于描述对应异常错误的对象实例。最后在应用程序中利用这个异常类对象实例处理应用的代码中可能发生的异常错误。
14.3.1 类类型中异常基本应用
类体系中异常的处理情况其实与函数中大致相仿。但由于自定义类型加入了封装、继承等特性,其异常处理变得稍微复杂。通常情况下,大型的软件系统中往往会自定义异常类,供其它模块程序包含使用。下面将讲解一个自定义异常类以及在类中使用该异常类的实例,读者可以了解异常在类体系中的应用情况与需要注意的问题。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1405_01.h、chapter1405_02.h与chapter1405_01.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1405
* 源文件chapter1405_01.h chapter1405_02.h chapter1405_02.cpp
* 异常类的封装
*/
#ifndef EXCEPTION_H_
#define EXCEPTION_H_
#include <iostream>
#include <string>
using namespace std;
//自定义异常类
class MyException
{
public:
//异常类无参数默认构造函数定义
MyException()
{
m_context= ""; //初始化异常信息数据成员为空
}
//异常类空析构函数定义
~MyException(){}
//异常类字符串类型参数构造函数定义
MyException(const string & right)
{
m_context= right; //初始化异常信息数据为字符串对象参数
}
//异常类字符串指针类型参数构造函数定义
MyException(const char * right)
{
m_context= right; //初始化异常信息数据为字符串指针参数
}
//异常类返回异常信息接口方法定义
stringwhat() const
{
returnm_context; //直接返回相应的异常信息数据成员
}
private:
string m_context; //字符串类型数据成员,表示异常信息
};
#endif
//异常测试类,除法计算类头文件chapter1405_02.h
#include "chapter1405_01.h"
//自定义除法计算类
class Compute
{
public:
Compute(intvalue1,int value2); //除法计算类构造函数,两个整型参数列表
~Compute(){} //除法计算类空析构函数定义
voiddivideCompute(); //除法计算类除法计算方法成员
private:
intm_value1; //除法计算类数据成员,表示被除数
intm_value2; //除法计算类数据成员,表示除数
};
//异常测试类,除法计算类源文件chapter1405_02.cpp
#include "chapter1405_02.h"
Compute::Compute(int value1,int value2) //除法计算类构造函数定义
{
m_value1= value1; //初始化被除数数据成员
m_value2= value2; //初始化除数数据成员
}
void Compute::divideCompute() //除法类除法计算方法成员定义
{
try
{
if(m_value2== 0) //如果除数为0,则抛出相应异常
{
throwMyException("divisor is 0!");
}
else //否则计算两数相除,并打印结果
{
cout<<"m_value1/m_value2:"<<(m_value1/m_value2)<<endl;
}
}catch(MyException&e) //捕获对应异常类型
{
stringerrorMessage = e.what(); //定义错误信息字符串对象,获取对应异常信息
cout<<errorMessage<<endl; //打印输出对应的异常信息
}catch(...) //捕获所有异常
{
stringerrorMessage = "Don’t knowerror!";//捕获所有异常处理信息
cout<<errorMessage<<endl;
}
}
int main()
{
intvalue1,value2; //定义两个整型变量
cout<<"Pleaseinput two value:"<<endl; //提述输入两个整型数据
cin>>value1>>value2; //从键盘输入两个整型数
Computecompute(value1,value2); //定义计算类对象,同时传入输入的两个整型数构造初始化
compute.divideCompute(); //计算类对象调用其除法计算方法
return0;
}
本实例通过封装异常处理类类型,演示异常处理在C++应用程序中的使用方法。程序主要由异常类、程序计算类以及主函数组成,具体剖析见程序注释与后面讲解。
2.编辑makefile
Linux平台下需要编译源文件为chapter1405_02.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1405_02.o
CC=g++
chapter1405_02: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1405_02
clean:
rm-f chapter1405_02 core $(OBJECTS)
submit:
cp-f -r chapter1405_02 ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer@localhost src]$ make
g++ -c-o chapter1405_02.o chapter1405_02.cpp
g++ chapter1405_02.o -g -o chapter1405_02
[developer@localhost src]$ make submit
cp -f -r chapter1405_02 ../bin
cp -f -r *.h ../include
[developer@localhostsrc]$ cd ../bin
[developer@localhostbin]$ ./chapter1405_02
Please input two value:
12
0
divisor is 0!
上述实例中主要演示了异常机制在C++面向对象类类型中的应用情况。功能与前面除数为0的实例相仿。本实例中首先定义异常类MyException。该类主要用于用于异常抛出和捕捉时的对象类型。异常类中主要包含成员:四个不同类型的构函数、一个获取异常信息的what方法成员、一个私有保护型的数据成员用于表示异常信息内容。
MyException类中3个构造函数,第一个为无参数默认构造定义,将异常信息内容初始化为空。第二个则参数为string型的对象构造,使用字符串初始化数据成员。第三个则参数为字符串指针的对象构造,使用字符串指针初始化异常数据成员。异常类中的方法成员what中主要返回异常信息,用于在应用程序中捕获并打印输出。
计算类主要用于两数除法运算。该类中主要包含一个构造函数、一个空析构函数以及一个计算两个数除法的方法。另外,它包含两个整型的数据成员分别表示除法中的两个数。该类的构造函数中主要通过传入的两个整型参数初始化对应的数据成员,计算除法的方法实现与前面的除法计算实例相同,仅仅是抛出的异常类型为MyException。另外捕捉异常catch块中应用了异常类型对象实例调用其what方法获取对应的异常信息并赋值最后输出。
主程序中从键盘输入两个整型数,分别为12和0。定义Compute类对象实例,通过实参初始化计算类数据成员,随后调用其除法计算方法。除法计算方法中由于除数为0,则抛出自定义异常类型同时传入异常错误信息,随后catch块中捕获到对应类型异常,内部则调用自定义异常类中what方法获取异常出错信息,最后打印输出。
14.3.2 异常类的继承处理
异常机制在继承体系中依然可以很好的运用。最重要的是利用类继承体系与对应的虚拟方法实现异常的多态性。通过自定义异常类派生新的异常类,除了包含基类基本异常处理方法,还可以包含自定义添加新的异常处理方式。下面将会在上述实例中添加异常派生类。新的异常派生类不仅仅可以在异常处抛出对应的错误信息,还可以支持错误代码及对应的信息。
/**
* 实例chapter1406
* 源文件chapter1406_01.h
* 异常继承类的封装
*/
//异常处理类源文件chapter1406_01.h
#ifndef EXCEPTION_H_
#define EXCEPTION_H_
#include <iostream>
#include <string>
using namespace std;
//自定义异常类
class MyException
{
public:
MyException() //自定义异常类构造函数定义
{
m_context= ""; //初始化类异常信息数据成员为空
}
virtual~MyException(){} //异常类析构函数定义
MyException(const string & right) //异常类构造函数定义,通过传入字符串型参数初始化
{
m_context= right;
}
MyException(const char * right) //异常类构造函数定义,通过传入字符串指针参数初始化
{
m_context= right;
}
virtualstring what() const //异常类中虚拟what接口方法定义,获取异常信息
{
returnm_context;
}
protected:
string m_context; //异常类保护型数据成员,表示异常信息内容
};
//异常派生类,加入错误代码处理
class code_MyException : public MyException
{
public:
code_MyException(interror_code, string error_code_msg = "", string context ="")//异常派生类构造
{
m_errorCode= error_code; //错误代码初始化
m_errorCodeMsg= error_code_msg; //错误代码对应消息初始化
m_context= context; //对应异常信息初始化
}
stringwhat() const //派生类what接口重定义
{
chartmp[20]; //定义临时字符数组
sprintf(tmp,"%d", m_errorCode); //格式化错误代码
returnstring(tmp) + "," + m_errorCodeMsg + "," + m_context; //返回连接的错误信息
}
intgetErrorCode() const //获取错误代码
{
returnm_errorCode;
}
stringgetErrorCodeMsg() const //获取错误代码对应信息
{
returnm_errorCodeMsg;
}
protected:
int m_errorCode; //错误代码
string m_errorCodeMsg; //错误信息
};
#endif
上述实例中在原来异常类的基础之上派生了新的异常处理类,其中增加了错误代码以及错误信息的功能。利用C++继承机制,同时将其中的析构方法以及what接口定义为虚拟函数,有助于统一接口的实现。封装实现的异常处理类可以将其编译为库,作为系统中统一的组件供程序来使用。该实例的实际应用以及测试由初学者按照前面实例的方式来实践操作,从中领悟C++多态以及异常处理的好处。
14.4 标准库提供异常处理类
C++标准库同样对异常作出了封装实现。标准库中异常实现也采用类继承层次式的结构。库中针对异常处理提供了一个层次化的类结构。该类层次中顶层基类为exception,即针对其它不同情况下的异常处理类都派生自exception类。
针对异常处理,总共提供了八个标准异常处理。其中,四个针对语言级提供的抛出异常,分别为bad_alloc、bad_cast、bad_typeid以及bad_exception;另外四个为C++标准库抛出的异常,分别是out_of_range、invalid_argument、overflow_error以及ios_base::failure。下面将会针对这八类抛出异常情况作详细的讲述,分别说明每类异常表达的意义及基本用法。
1. bad_alloc
bad_alloc针对C++中动态存储分配new的操作提供的异常信号,主要针对分配内存空间时内存耗尽(分配不足)时抛出的异常。这种异常的抛出有利于开发者在实际应用中针对new操作分配内存时提供某种分配出错的处理方式。下面可以通过一个基本小代码片段来理解bad_alloc异常处理情况。
void newChar()
{
try
{
newchar[10000000000000]; //通过new开辟一个超大的内存空间
}catch(bad_alloc)
{
cout<<”memoryerror!”<<endl;
}
}
上述代码动态new了一个很大的内容空间,当本地计算机内存不足时会默认抛出bad_alloc异常。随后catch捕捉到该异常后进入对应的异常处理块中。
2.bad_cast
C++多态提供的dynamic_cast应用于指针类型时,通常需要考虑指针各类异常的情况。这时,可以显式的去判断处理。但是当dynamic_cast应用于引用时,即dynamic_cast<Type&>(object)表示引用的类型为Type,如果引用的不是对应类型的对象,则dynamic_cast将会默认隐式的作断言检查。这时就会抛出bad_cast异常,供catch捕获检测处理。
3. bad_typeid
bad_typeid是针对dynamic_cast操作空指针抛出的异常。
4. bad_exception
当应用程序出现了无法catch捕获的异常情况时,通常会调用terminate终止程序。而使用bad_exception时,出现类似情况则会抛出bad_exception异常,用来替代终止程序。
bad_exception异常处理则主要针对应用提出的。这些类同样从exception类派生而来。out_of_range表示操作数组等结构下标时,访问超过设定范围所抛出的异常。它主要针对容器中at()方法与重载的下标访问运算符两类方法。invalid_argument则表示向函数传递无效参数时抛出的异常,开发者可以针对该类抛出的异常作出相应的处理。overflow_error表示运算时候发生的上溢情况抛出的异常。ios_base::failure则是针对输出输入流操作时抛出的异常。
标准库中异常处理的封装可以通过查阅相关标准文档,根据提供的相关处理接口在实际的应用程序中使用。实际的exception基类中除了基本的构造函数外,重载实现一些运算符操作。最重要的是其what接口的定义,该方法定义为虚拟方法供其派生类中改写增加更多的异常处理。
14.5 C++应用程序错误处理
C++应用软件系统开发中,通常情况下可以通过实现错误处理类来记录程序启动以及处理中可能出现的错误情况。错误处理采用自定义类类型来实现,除了起到异常处理的补充作用以外对于程序中必不可少的异常容错处理来讲,错误处理相对效率较高些。下面本小节主要通过自定义实现一个错误处理类的实例,详细讲述软件应用程序中针对错误处理的另外一种错误信息提示方式。
1.准备实例
打开UE工具,创建新的空文件并另存为chapter1407_01.h、chapter1407_01.cpp、chapter1407_02.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
//错误处理类头文件chapter1407_01.h
#ifndef ERRORHANDLE_H_
#define ERRORHANDLE_H_
#include <iostream>
#include <string>
#include <deque>
using namespace std;
const int ERRORMSG_LEN = 300; //错误信息长度常量定义
class ErrorInfo //错误信息类
{
public:
ErrorInfo(){} //错误信息空构造函数定义
~ErrorInfo(){} //错误信息空析构函数定义
ErrorInfo(constErrorInfo& errorinfo); //错误信息类拷贝构造
public:
intm_errorNo; //错误信息类数据成员,表示错误代码
charm_message[ERRORMSG_LEN+1]; //错误信息类数据成员,表示错误信息
};
class ErrorMessages //错误消息容器类
{
public:
voidsetProgramInfo(const string& programInfoName); //错误消息容器类设置应用程序信息方法
stringgetProgramInfo(); //错误消息容器类获取应用程序信息方法
voidinsert(ErrorInfo &errInfo); //错误消息容器类插入错误信息方法
boolget(ErrorInfo& errInfo); //错误信息容器类获取错误信息方法
voidclean(); //清空错误消息队列方法
boolisEmpty(); //判断错误消息队列是否为空
voidinsert(const int errorCode, const string& errorMessage); //重载插入错误信息方法
voidinsert(const int errorCode, const char *errorMessage); //重载插入错误信息方法
private:
stringm_programInfoName; //错误消息类数据成员,表示应用程序名
deque<ErrorInfo>m_errorInfoQueue; //错误消息类数据成员,表述错误信息队列
};
#endif
//错误处理类源文件chapter1407_01.cpp
#include "chapter1407_01.h"
ErrorInfo::ErrorInfo(const ErrorInfo&errorinfo) //错误信息类拷贝构造定义
{
m_errorNo= errorinfo.m_errorNo; //拷贝传入错误信息类对象的成员
strcpy(m_message,errorinfo.m_message);
}
void ErrorMessages::setProgramInfo(conststring& programInfoName) //设置应用程序信息方法定义
{
m_programInfoName= programInfoName; //根据传入的程序名设置
}
string ErrorMessages::getProgramInfo() //获取应用程序信息
{
returnm_programInfoName; //返回应用程序名称信息
}
void ErrorMessages::insert(ErrorInfo&errorinfo) //插入错误信息方法定义
{
m_errorInfoQueue.push_back(errorinfo); //将传入的错误信息类对象放入错误信息队列
}
void ErrorMessages::insert(const int errorCode,const string& errorMessage)//重载插入错误信息方法定义
{
ErrorInfoerrorinfo; //定义错误信息类对象实例
errorinfo.m_errorNo= errorCode; //将传入的错误代码赋值给该对象中数据成员
if(errorMessage.length() <= ERRORMSG_LEN) //如果传入的错误信息长度在规定之内
strcpy(errorinfo.m_message,errorMessage.c_str()); //则将错误信息拷贝到该对象对应数据成员中
else //否则扩充长度后再拷贝
{
strncpy(errorinfo.m_message,errorMessage.c_str(), ERRORMSG_LEN);
errorinfo.m_message[ERRORMSG_LEN]= 0;
}
insert(errorinfo); //最后插入到错误消息队列中
}
void ErrorMessages::insert(const int errorCode,const char *errorMessage)//插入错误信息重载方法定义
{
ErrorInfoerrorinfo; //定义错误信息类对象
errorinfo.m_errorNo= errorCode; //同上一方法定义
if(strlen(errorMessage) <= ERRORMSG_LEN)
strcpy(errorinfo.m_message,errorMessage);
else
{
strncpy(errorinfo.m_message,errorMessage, ERRORMSG_LEN);
errorinfo.m_message[ERRORMSG_LEN]= 0;
}
insert(errorinfo);
}
bool ErrorMessages::get(ErrorInfo& errorinfo) //获取错误信息队列中的信息
{
if(m_errorInfoQueue.empty()) //如果错误信息队列为空,则直接返回false
returnfalse;
errorinfo= m_errorInfoQueue.front(); //返回队列中头部的数据
m_errorInfoQueue.pop_front(); //删除队列中头部的数据
returntrue;
}
void ErrorMessages::clean() //清空错误信息队列
{
m_errorInfoQueue.clear(); //直接调用clear方法清除
}
bool ErrorMessages::isEmpty() //判断错误信息是否为空
{
returnm_errorInfoQueue.empty(); //直接调用empty方法判断
}
//错误处理类测试源文件chapter1407_02.cpp
#include "chapter1407_01.h"
ErrorMessages errorMessages; //定义错误消息容器类对象
const int E_DIVISOR_ZERO = 200; //定义除数为0的错误代码常量
const string E_DIVISOR_ERROR = "divisor is0!"; //定义除数为0的错误信息常量
void divideCompute(int value1,int value2) //除法计算函数定义
{
if(value2== 0) //如果除数为0,则记录相应的错误信息
{
errorMessages.insert(E_DIVISOR_ZERO,E_DIVISOR_ERROR);//插入对应的错误代码与错误信息
}
else //否则计算除法并输出结果
{
cout<<"value1/value2:"<<(value1/value2)<<endl;
}
}
int main(int argc,char *argv[])
{
stringname; //定义字符串对象name
ErrorInfoerrorInfo; //定义错误信息类
name= argv[0]; //获取程序名并初始化字符串name
errorMessages.setProgramInfo(name); //调用错误信息容器类中设置程序信息方法
cout<<"Programname:"<<errorMessages.getProgramInfo()<<endl;//打印输出该程序信息
intvalue1,value2; //定义两个整型变量
cout<<"Pleaseinput two value:"<<endl; //提示输入两个整型参数
cin>>value1>>value2; //从键盘输入两个整型数
divideCompute(value1,value2); //调用计算除法运算的函数计算输入的数据
errorMessages.get(errorInfo); //获取错误消息队列中的数据
cout<<"errorcode:"<<errorInfo.m_errorNo<<" "
<<"errormessage:"<<errorInfo.m_message<<endl; //打印输出对应队列中的错误信息
return0;
}
本实例主要通过封装程序错误处理类,演示应用程序中错误处理的一般实现方式。程序主要由错误处理类与主函数组成,程序具体剖析见程序注释与后面的讲解。
2.编辑makefile
Linux平台下需要编译源文件为chapter1407_01.cpp、chapter1407_02.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1407_01.o chapter1407_02.o
CC=g++
chapter1407_02: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1407_02
chapter1407_01.o:chapter1407_01.h
chapter1407_02.o:
clean:
rm-f chapter1407_02 core $(OBJECTS)
submit:
cp-f -r chapter1407_02 ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer@localhost src]$ make
g++ -c -ochapter1407_01.o chapter1407_01.cpp
g++ -c -ochapter1407_02.o chapter1407_02.cpp
g++ chapter1407_01.o chapter1407_02.o -g -ochapter1407_02
[developer@localhost src]$ make submit
cp -f -r chapter1407_02 ../bin
cp -f -r *.h ../include
[developer@localhostsrc]$ cd ../bin
[developer@localhostbin]$ ./chapter1407_02
Program name:./chapter1407_02
Please input two value:
12
0
error code:200 error message:divisor is 0!
本实例中主要定义实现错误处理类。该类用于记录软件系统中自定义的错误代码与对应的错误信息,以配合异常实现应用程序相应的错误处理。上述实例中错误处理主要涉及两个类的定义:
q 一个为记录错误信息类ErrorInfo。其中,主要包括相应的构造析构函数以及一个错误信息对象拷贝方法。它还包含两个公开的数据成员,用于分别表示错误代码与对应的错误信息。
q 另一个为错误信息容器类ErrorMessage。该类主要用于记录并存放相应的错误处理信息,并提供相应的错误信息访问接口,以供应用程序使用。
ErrorMessage类中主要包括八个接口方法与两个数据成员。该类的接口方法在注释中作了详细的标注。其中,这些接口中最重要为几个insert方法的重载实现,从而实现了不同种情况下的信息插入处理。第一个是整个结构体对象参数接口;第二个为支持错误代码与字符串类型的对象接口;最后一个为支持错误代码与字符串常量的插入信息接口。其中,还包括两个保护数据成员,分别为应用程序名称m_programInfoName和存放错误消息的队列m_errorInfoQueue。该队列中存放的为错误信息类类型的数据。
主程序中则仍然主要以测试除法运算中除数为0时的错误处理。首先从程序信息argv参数数组中获取程序名称设置并获取打印该信息。随后根据传入的两个整型数,调用对应的计算除法的运算方法。该方法中,一旦除数为0,则记录对应的错误信息。最后主程序中则获取到对应的错误信息并打印输出。
上述程序中错误处理类可以放入自定义库或者组件中,供应用程序中记录错误信息使用。其中,支持错误代码以及对应的错误信息对应记录。这样做的好处是针对不同的模块设计可以规定一套错误代码以及对应信息,供整个系统使用。另外,插入的错误信息可以根据实际需求,将其记录到日志文件中,供出现错误时查询使用。
14.6 小结
C++异常处理的提出为编写出更具健壮性、稳定性的应用程序提供了很好的支持。但是异常与前面介绍该语言的特性一样,不能够滥用。因为任何支持的特性总是以牺牲一定的效率来实现的。通过上述介绍,用户在实际软件系统中可以结合错误处理、异常以及对应的错误日志等,最终开发出高稳定、高容错性的应用系统。