摘 要:在C++编程过程中,很容易出现内存泄露等安全问题,而导致这些问题的核心原因就是在于没有管理好对象生命周期。本文主要对C++编程中对象生命周期管理的几种方式进行分析并提出自己的见解。
关键词:C++编程;对象;生命周期 C++编程中对象生命周期管理主要包括生成、使用和消除三个阶段。对象不仅可以改变自己变量的状态,而且还拥有使用创建它的那个类中方法的能力,对象通过使用这些方法可以产生一定的行为。一个对象的生命周期结束而对象却没被释放,那么内存泄露问题肯定会产生,因此,做好对象生命周期管理工作非常重要。1. 业务逻辑从理论上讲,应用程序中申请的内存一般都有其作用域,当对象已经完成其业务逻辑后,需要将其释放,避免造成内存泄露(全局对象除外)。例如,视图和文档,当视图对象已经完成其业务逻辑后,用户关闭对应窗口时,需要将其进行释放,而文档对象可以在所有视图对象释放时释放。因此,系统代码就可以直接根据业务逻辑来编写,这样的代码不仅效率高,而且容易理解。虽然采用这种方法直接编写代码有很多优势,但是随着需求的不断增加,系统规模将会越来越大,也就越来越复杂,因此,上述那种简单的方法不适用于复杂的系统。例如,在上面的例子中加一个关闭文档的操作,使用这个命令时,将文档直接关闭,可此时视图也有可能是开着的,关闭文档需要将视图先关闭,在视图对象释放时进行文档释放,那么此时用户关闭对应的窗口就变成了视图对象的释放条件。在一个复杂的系统中,一个对象要想释放需要满足很多条件才能实现,此时都是按照业务逻辑来判断程序,出错则不可避免。由此可知,用业务逻辑来管理对象生命周期的方式仅适用于简单对象系统。因此,笔者认为使对象生命周期的管理更加合理和简化,需要设计另一些机制。2. 释放通知 如果对文档对象进行释放,将其释放通知发送给相关的视图对象,其二者关系是非常密切的。如果视图对象暂时不释放,可以清除文档对象的引用,如若视图对象释放了,其结果也不会发生任何变化。当创建好文档和视图后,引用是相互的,这里先从单项开始,视图引用文档:(1)文档的指针被视图获得;(2)文档的regFreeNotifiC++ation方法被视图调用,文档自己将会充当参数,文档对象释放的通知列表中将会把文档自己加入其中,也就是说,文档对象释放时,将其释放的通知,需要让列表中的所有对象都能够知道;(3)此时文档对象的指针,不会有什么不稳定的现象产生,视图对象就可以安安心心、大大方方的拥有;(4)如果文档对象释放了,将其通知给所有的列表对象,并且让所有的对象进行调用,由于视图刚才已经注册过了,因此视图也包括在内。对列表中对象的freeNotifiC++ation方法进行调用就是通知的方法,充分利用这个方法,在视图的这个方法中,将文档的引用清除掉;(5)如果先释放的不是文档对象,而是视图对象,此时文档对象中的unregFreeNotifiC++ation方法就要被执行,然后从列表中移除自己。 在Delphi语言的VC++L库中同样可采用这种方法。上述三个方法在VC++L的TC++omponent类上都被抽象了,只不过名称不同而已,因此通过采用这种方法TC++omponent类及其子类的对象之间可以相互引用,并且能保证像野指针这类问题是不会出现的。这种方法虽然有这么多的优势,但是其也具有一定的缺点,使其在使用时受到制约,例如,成本高、效率不高等。因此,这种方法在简单、高等级对象中比较适合使用。抽象对象引用以及后续的释放环节是释放通知机制的最大意义,使这个机制和业务逻辑本身无关。这样,无论业务逻辑多么复杂,开发人员只要严格遵守这个机制,都可以确保野指针问题不会出现,从而使开发人员的工作量大大减轻,并且开发效率更高、程序质量更好。3. 引用计数由上述分析可以看出,释放通知虽然在生命周期管理中是一个很好的机制,但是也存在一些不足。例如,每个对象需要维护一个列表,可否不对列表进行维护,而对其进一步抽象呢?在上文中已经提出每个对象释放时机都不同,而这些释放时机的决定因素有很多,其中业务逻辑是最重要的,但是这个合理的释放时机的确定是随着业务逻辑的复杂性越来越复杂,已经不是由单一的条件来决定,而是由多个条件同时制约。因此,要想满足释放必须满足所有的条件,解决这个问题的关键在于判断这些条件。针对对象释放,只关心这个条件能够满足释放要求,而对具体业务条件是什么不做重点考虑。因此,诞生了引用计数的机制,每一个对象由一个计数器,当加一时就说明释放的条件并没有满足条件,当减一时就说明释放条件已经满足条件,如果计数器为0就说明所有的条件都满足,对象可以释放了。如果文档被视图引用的话,那么文档的addRef方法被视图调用,计数器增1;若文档被引用的视图释放,则文档的release方法就被视图调用,同时计数器减1。当计数器为0时,文档对象释放。但是如果在这个过程中文档被其他视图使用,那么addRef方法也被那个视图执行,这样就不会将文档对象释放,继续由那个视图使用,如此循环,直到release方法被那个视图使用为止。[0] class BaseRefObject { public: BaseRefObject():m_nRefCount(0) { } void addRef() { ++m_nRefCount; } void release() { if (0 == m_RefCount) { return; } else { --m_nRefCount; delete this; } private: int m_nRefCount; } 这种方法优势较明显,其方式简单、效率高,可以在大规模对象系统中使用,但是同时也存在一些不足,具体表现在以下几个方面:(1)文档对象的释放都是被动的,影响其因素很多,尤其是外界因素,即使业务上要求文档关闭,它也不会关闭,但是可以保持文档的内容且把其作为一种标志来放置,从而表明其已经关闭。此时如果文档的方法再被视图调用的话,直接返回错误。另外,这个过程的完成还可以借助使用事件的机制辅助来进行,就是需要在关闭时,向视图发送事件,让它将引用解除,这个步骤又与释放通知有些相似;(2)循环引用问题。上面的例子都只是假设文档被视图引用,而事实上视图也可以被文档引用。那么如果视图被文档引用的话,就会出现两个对象相互引用,此时如果没有外力作用,两个对象的引用计数都不会变为0,于是内存泄漏现象就由此产生了,它的产生是任何开发者都不愿意看到的。总之,引用计数也是一种机制,在微软的C++OM中这种机制经常被使用,它与业务逻辑没有任何关系。4. 垃圾回收野指针不是NULL指针而是指向非法内存的指针,产生野指针的原因主要有:(1)没有初始化指针变量。任何指针变量刚被创建时不会自动成为NULL指针,它的默认值是随机的,它会乱指一气。因此,指针变量初始化是要么将指针设置为NULL,要么让它指向有效内存。(2)指针p被free或者delete之后没有置为NULL,让人误认为p仍是一个有效的指针。(3)指针操作超越了变量的作用范围。其实避免野指针发生的一个简单方法就是对象不释放,但是对象不释放会导致另一个问题产生--内存泄露,一旦出现了野指针,程序就会被迫停止运行,而内存泄露则不会导致程序立刻停止,它等到没有资源再消耗时才停止。因此,为了避免野指针的发生,可暂时不管内存泄露问题,等到出现资源不足时再想其他办法(如资源不足时,找出那些垃圾,将这些垃圾对象释放掉,将其内存给其他对象用),这是一个比较简单的方法。查找垃圾所采用的算法如下:首先,假设所有的对象都是垃圾;其次,利用全局变量和局部变量找出非垃圾对象,也就是找出没有被它们引用的对象;再次,再从第二步找出的非垃圾对象中找出不是垃圾的对象,其方法就是非垃圾对象引用的对象也不是垃圾;第四步,将第三步骤循环下去,直到没有新的非垃圾对象产生为止;最后,剩下的就都是垃圾。 由此可以看出,这种算法是非常简单的,但是在C++中,全局变量、局部变量和成员变量不全是对象,此时这个算法就有些不合适了,在JAVA等这些语言中都是对象,此时需要语言级的支撑。到目前为止,最好的内存管理机制也就是垃圾回收了,其避免野指针的产生是不需要人工来参与的,它也不会产生内存泄露等问题,其效率是非常高的,但是需要花费大量的成本。 结语 业务逻辑决定了对象的生命周期。事实上,业务逻辑是不断变化的,朝着复杂的方向发展,此时具体到某个对象其释放的条件可能比较复杂,因此,需要对这些对象的生命周期的管理需要引进一些专门的机制来进行,由此,释放通知、引用计数、垃圾回收等机制就产生了。之所以产生如此多的机制,那是因为没有一个机制能够完全解决一切问题,每个机制都有优点和不足。笔者通过多年来的工作和学习发现,用的最多的是引用计数,但是在使用的同时,也经常会出现循环引用的问题。由此可见,最好的方法就是:将引用计数作为基础,在合适的地方根据业务逻辑加入释放通知(这里主要使用事件通知,其实事件本身是为了业务的需要而设计,并非是为了对象释放,但从本质上讲业务决定着对象的生命周期)。 生命周期管理中这些机制的引用的原因有很多,其中一个重要原因就是减少一些内存问题的发生,并且达到高效率、高质量的目标。内存泄露是普遍存在且必须解决的问题,而本文所提到的这些机制没有一个能真正解决内存泄露问题。因此,现阶段程序员对此问题进行更深入的研究,将业务逻辑和编程事务性工作同时抓,同等程度重视二者。参考文献:. C++omputer Integrated ManufaC++turing Systems-C++IMS, 2004, (01).
中国论文网(www.lunwen.net.cn)免费学术期刊论文发表,目录,论文查重入口,本科毕业论文怎么写,职称论文范文,论文摘要,论文文献资料,毕业论文格式,论文检测降重服务。