首页 理论教育 资源管理方法及其相关性质

资源管理方法及其相关性质

时间:2023-10-25 理论教育 版权反馈
【摘要】:该技术依赖于构造函数和析构函数的性质以及这两个函数与异常处理的相互关系。当某个对象的构造函数执行完毕时,此对象才被认为已经明确建立。在资源申请简单模型的支持下,编写构造函数的人不必去专门写显式的异常处理代码。抛出异常之前,复制构造函数需要释放已经申请到的所有资源。

资源管理方法及其相关性质

当函数申请了其所占有的资源时,为保证系统的运行,必须正确地释放此类资源,即通常所说的“正确释放”,即要求申请资源的函数在返回其调用者之前完成资源的释放。例如,一旦在调用fopen()函数之后,调用fclose()函数之前,程序运行时出了问题,会出现异常退出,但此时却没有来得及调用fclose()函数。即使是常规的return语句实现问题,导致use_file()退出而没有关闭该文件。

在使用时,程序员可以将所有出现异常的语句都包裹在一个try块内,在关闭文件时重新抛出这个异常。此方法过于烦琐,且代价高昂,容易诱发各类错误,让程序开发人员深感厌恶。所幸还有一种更好的解决办法。

类的对象通常由构造函数创建,并由析构函数销毁。只要适当地利用带有构造函数和析构函数的类对象,即可以处理此种资源的申请和释放问题。例如,在使用文件时,程序员可以定义文件类,其中包含其构造函数和析构函数。一旦该类的对象在其作用域结束时将被销毁,析构函数会关闭相应的文件(当然,需要在析构函数中添加相应的代码)。无论函数是正常结束,还是非正常退出,析构函数总是会被调用的。异常处理机制使程序员可以从主要算法中删去处理错误的代码,从而使得代码更为简单。

1.构造函数和析构函数的使用

利用局部对象管理资源的技术通常被称为“资源申请即初始化”。该技术依赖于构造函数和析构函数的性质以及这两个函数与异常处理的相互关系。

当某个对象的构造函数执行完毕时,此对象才被认为已经明确建立。之后,堆栈回退时才为该对象调用析构函数。对于由子对象组成对象的构造函数,将持续到所有子对象的构造函数均完成,其构造函数才执行完毕。对于数组的构造函数,将持续到数组所有元素均构造完成,其构造函数才执行完毕。

对象的构造函数努力去保证对象能够完整、准确地创建起来。如果目标无法实现,书写良好的构造函数会将系统的状态恢复到对象构造之前的情况。理想状况是:按朴素的方式写出构造函数能达到的可能情况,不使它们处理的对象处于某种“片面构造”的状态。使用“资源申请即初始化”技术,可以实现完全构造类对象。

在资源申请简单模型的支持下,编写构造函数的人不必去专门写显式的异常处理代码。这样,程序员就不必去追踪其他情况,但这也带来了很多问题。例如,大量的初始化工作需要放在构造函数中才更加保险,才不会导致剧烈的异常给整个程序或操作系统带来致命的冲击。

2.STL的auto_ptr类

类auto_ptr的对象可以使用指针去初始化,且能够以指针同样的方式间接访问。在auto_ptr类对象退出作用域时,所指的对象将被隐式地自动删除。类auto_ptr型指针具有与常规指针不一样的复制意义:在将一个auto_ptr型指针复制给另一个指针之后,原来的auto_ptr不再指向任何东西。复制auto_ptr将导致对其自身的修改,但constauto_ptr不可以被复制。

类auto_ptr的声明形式为:

使用类auto_ptr时,需要包含头文件<memory>。多个auto_ptr拥有同一个对象的效果是无定义的。最可能的情况是使该对象无法被彻底删除。

任何程序都应具备从破坏中恢复的能力。不可能所有程序都需要付诸很多的努力——使用“资源申请即初始化”技术、auto_ptr和catch块等技术去保护该程序。对于一些简单程序,一旦发生异常,最有效的办法是使该进程夭折。此时,程序会释放所申请的所有资源,重新运行程序。

3.构造函数中的异常和new()

在类的构造函数中,如果大量使用new()函数分配存储空间,一旦发生异常,能够完整地释放这些存储空间吗?回答是肯定的。

但如果在程序中使用new()函数,那么在释放该内存空间时,即要使用delete()函数。

异常是从构造函数报告的问题中提供解决方案。由于构造函数不能顺利返回独立的值供调用程序检查,通常的解决办法有以下4种:

1)返回处于错误状态的对象,相信用户有办法检查其状态。

2)设置非局部变量指出创建失败,相信用户能检查该变量。(www.xing528.com)

3)在构造函数中不做初始化,依赖用户第一次使用对象之前调用某个初始化函数。

4)将对象标记为“未初始化”,让该对象调用的成员函数完成实际的初始化工作,并允许该函数在初始化失败时报告错误。

异常处理机制使构造失败信息能从构造函数内部传递出来。程序员可以安排一些throw(),例如,某类的对象遭遇超量存储时,可以在程序中使用判断语句,一旦遭遇此类情况,即可将其抛出。尤其是处理那些多种资源的构造函数,“资源申请即初始化”技术是最安全最优秀的方法。从根本上讲,该技术把处理多种资源的问题归结为一种反复应用处理单一资源技术的问题。

如果类的成员在初始化时抛出异常,按照默认的约定,该异常将传至类的构造函数位置。构造函数本身通常会将完整的函数体包括在try块内,函数本身是无法捕捉这些异常的。

另外,构造函数是无法复制的。复制构造函数可以通过抛出异常的方式发出失败信号。在此种情况下,实际上没有创建对象。例如,对于某容器类,在复制构造函数时需要分配存储并复制元素,可能导致异常的抛出。抛出异常之前,复制构造函数需要释放已经申请到的所有资源。尤其是复制赋值操作,会申请资源,也可能通过抛出异常而结束。但在抛出异常之前必须保证每个操作对象均保持在某种合法状态。

4.析构函数中的异常

从异常处理的角度来讲,析构函数中也可能发生异常。通常此时析构函数会在两种情况下被调用:

1)正常调用。作为某个作用域正常退出的结果,或者作为delete操作的结果。

2)在异常处理中被调用。在堆栈回退过程中,异常处理机制退出作用域时,其中包含析构函数的对象。

对于第二种情况,析构函数抛出异常是不被允许的。如果真的在析构函数中发生了异常的抛出,即是异常处理机制的重大失败。在析构函数中抛出异常而导致析构函数退出程序,这违背了STL的基本原则。

为防止上述情况的发生,程序员可以在析构函数中使用try块和catch块。例如,对于类C,其析构函数可以如下编写:

如果有异常被抛出之后未被捕捉,STL中的uncaught_exception()函数会返回true。此函数多用于析构函数中,调用时可获取合适的返回值,从而确定对象是被正常销毁还是作为堆栈回退的一部分。

5.资源耗尽

申请资源一旦失败,会导致很大的问题。例如,程序在打开文件时文件不存在,或者耗尽了所有自由存储空间。通常会有两种解决方式:

1)唤醒机制。请求调用某个程序,纠正问题,之后继续执行。

2)终止策略。结束当前计算,并返回某个调用程序。

对于第一种方式,预先需要准备一个调用程序来帮助处理某段未知的代码中出现的资源申请问题。对于第二种方式,调用程序必须准备好应付某个资源申请失败的情况。第二种形式更为简单一些,也更为实用。第二种方式能够使系统保持抽象层次之间的较好的隔离。采取终止策略时终止的不是整个程序而是其中个别的计算。“终止是传统的描述策略的术语,表示从“失败”的计算返回到与某个调用过程相关的错误处理器,而不试图去修复坏的状况,之后从检查出问题的那个位置继续。

在C++中,唤醒模型通常由函数调用机制支持,终止模型由异常处理机制支持。要抛出异常,就必须存在一个被抛出的对象。每个C++实现时都要求保留足够的存储空间,在存储空间耗尽时,需要抛出bad_alloc()。由于抛出其他对象而导致存储耗尽的情况也是会发生的。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈