关于C++中的异常处理使用方法与技巧

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

1关于异常处理

1.1为什么要抛出异常

抛出异常的好处一是可以不干扰正常的返回值,另一个是调用者必须处理异常,而不像以前c语言返回一个整数型的错误码,调用者往往将它忽略了。

C++的异常处理确保当程序的执行流程离开一个作用域的时候,对于属于这个作用域的所有由构造函数建立起来的对象,它们的析构函数一定会被调用。

1.2捕获所有异常

有时候,程序员可能希望创建一个异常处理器,使其能够捕获所有类型的异常。用省略号代替异常处理器的参数列表就可以实现这一点:

由于省略号异常处理器能够捕获任何类型的异常,所以最好将它放在异常处理器列表的最后,从而避免架空它后面的异常处理器。

省略号异常处理器不允许接受任何参数,所以无法得到任何有关异常的信息,也无法知道异常的类型。它是一个“全能捕获者”。这种catch子句经常用于清理资源并重新抛出所捕获的异常。

1.3重新抛出异常

当需要释放某些资源时,例如网络连接或位于堆上的内存需要释放时,通常希望重新抛出一个异常。(详见本章后面的“资源管理”一节。)如果发生了异常,读者不必关心到底

是什么错误导致了异常的发生——只需要关闭以前打开的一个连接。此后,读者希望在某些更接近用户的语境(也就是说,在调用链中的更高层次)中对异常进行处理。在这种情况下,省略号异常处理器正符合这种的要求。这种处理方法,可以捕获所有异常,清理相关资源,然后重新抛出该异常,以使得其他地方的异常处理器能够处理该异常。在一个异常处理器内部,使

用不带参数的throw语句可以重新抛出异常:

与同一个try块相关的随后的catch子句仍然会被忽略—throw子句把这个异常传递给

位于更高一层语境中的异常处理器。另外,这个异常对象的所有信息都会保留,所以位于更高层语境中的捕获特定类型异常的异常处理器能够获取这个对象包含的所有信息。

1.4构造函数中的异常

C++规定构造函数抛出异常之后,对象将不被创建,析构函数也不会被执行,但已经创建成功的部分(比如一个类成员变量)会被部分逆序析构,不会产生内存泄漏。但有些资源需要在抛出异常前自己清理掉,比如打开成功的一个文件,最好关闭掉再抛出异常(虽然系统也会把这个资源回收),因为抛出异常之后析构函数不会被执行了。

(1)C++中通知对象构造失败的唯一方法那就是在构造函数中抛出异常;(这句话并不是说我们只有这个方法才能让上层知道构造函数失败,虽然构造函数没有返回值,我们完全可以在构造函数中传入一个引用值,然后在里面设置状态,运行完构造函数之后任然可以知道是否失败,但这种情况下面对象其实还是被构造出来的,只是里面有资源分配失败而已,并且析构函数还是会执行。这和我们构造失败不生成对象的初衷不符。)

(2)构造函数中抛出异常将导致对象的析构函数不被执行;(但已经生产的部分成员变量还是会被逆向析构的)

(3)当对象发生部分构造时,已经构造完毕的子对象将会逆序地被析构;

对于构造函数可能失败的做法一般有两种

1. 在构造函数中抛出异常,本对象构造未完成,它的析构函数不会被调用。当然,我们有义务释放已经分配到的资源。简单,最常见。

2. 把资源的初始化工作放在另一个单独函数中,比如 bool init(...),由对象创建者(比如工厂方法)先调用构造函数,再调用init方法。ATL中常见。

1.5析构函数中的异常

C++标准指明析构函数不能、也不应该抛出异常。在析构函数中抛出错误,通常被认为象征着拙劣的设计或糟糕的编码。这是因为:析构函数如果抛出异常,将会导致调用标准库terminate函数,而terminate函数将调用abort函数,导致程序的非正常退出。所以析构函数应该从不抛出异常。

析构函数不能抛出异常的理由:

1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成资源泄漏。

2)通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃。

那么当无法保证在析构函数中不发生异常时,该怎么办? 其实还是有很好办法来解决的。那就是把异常完全封装在析构函数内部,决不让异常抛出函数之外。这是一种非常简单,也非常有效的方法。

~ClassName()

{

try{

do_something();

}

catch(...){ //这里可以什么都不做,只是保证catch块的程序抛出的异常不会被扔出析构函数之外。

}

}

1.6不会抛出的异常

C++为什么抓不到除0错的“异常”?说起来,和原生数组访问越界为什么不是异常并无两样,主要还是为了“效率/性能”。对于大多数时候的除法操作,我们会让它出现除数为0的可能性很小,当你认为有这种可能,就自己检查吧,然后自己定义一个除0错的异常。

1.7自定义异常

C++可以返回任意类型异常,包括标准异常类、基本数据类型、用户自定义的任意类(不一定要从excption继承。但是,如果要使用统一的基类捕捉异常,就必须从excption继承,且继承时要注意以下问题:

异常信息应该通过构造函数传入,最好通过what()函数来返回。示例如下:

class CtpException:public QException

{

public:

CtpException(QString& className,QString&funcName,

QString&locationMark&,QString errMsg)

{

msg.append(className);

msg.append(".");

msg.append(funcName);

msg.append(".");

msg.append(locationMark);

msg.append(",发生异常:");

msg.append(errMsg);

}

constchar*what()constthrow()

{

return msg.constData();

}

private:

QByteArray msg;

};

1.8通过引用捕获异常

不要使用new来抛出异常,不要使用指针或者传值的方式捕获异常。

应该直接使用throw CtpException抛出异常,表示将异常放到栈中,就可以自动释放。

应该使用std::exception&来捕获异常,一定要写&,这样才能防止异常拷贝,并且可以做到使用父类对象捕获子类的异常信息。

try{

QString clsName="MainWindow";

QString funName="on_pushButton_3_clicked()";

QString errMsg="自定义错误";

QString locationMark="location-1";

throw CtpException(clsName,funName,errMsg,locationMark);

}

相关文档
最新文档