因为这一技术是如此有用,标准C++库提供了模板类auto_ptr,能自动包装动态分配的对象。void doSomething3(void) {auto_ptr p (new Node);// Access the Node as p->...// Node automatically deleted at end}在Java中配平资源 与C++不同,Java实现的是自动对象析构的一种“懒惰”形式。未被引用的对象被认为是垃圾收集的候选者,如果垃圾收集器回收它们,它们的finalize方法就会被调用。尽管这为开发者提供了便利,他们不再须要为大多数内存泄漏承受指责,但同时也使得实现C++方式的资源清理变得很困难。幸运的是,Java语言的设计者考虑周详地增加了一种语言特性进行补偿:finally子句。当try块含有finally子句时,如果try块中有任何语句被执行,该子句中的代码就保证会被执行。是否有异常抛出没有影响(即或try块中的代码执行了return语句)——finally子句中的代码都将会运行。这意味着我们可以通过这样的代码配平我们的资源使用:public void doSomething() throws IOException {File tmpFile = new File(tmpFileName);FileWriter tmp = new FileWriter(tmpFile);try {// do some work}finally {tmpFlete();}} 该例程使用了一个临时文件,不管例程怎样退出,我们都要删除该文件。finally块使得我们能够简洁地表达这一意图。当你无法配平资源时 有时基本的资源分配模式并不合适。这通常会出现在使用动态数据结构的程序中。一个例程将分配一块内存区,并把它链接进某个更大的数据结构中,这块内存可能会在那里呆上一段时间。 这里的诀窍是为内存分配设立一个语义不变项。你须要决定谁为某个聚集数据结构(aggregate data structure)中的数据负责。当你解除顶层结构的分配时会发生什么?你有三个主要选择:1. 顶层结构还负责释放它包含的任何子结构。这些结构随即递归地删除它们包含的数据,等等。2. 只是解除顶层结构的分配。它指向的(没有在别处引用的)任何结构都会被遗弃。3. 如果顶层结构含有任何子结构,它就拒绝解除自身的分配。 这里的选择取决于每个数据结构自身的情形。但是,对于每个结构,你都须明确做出选择,并始终如一地实现你的选择。在像C这样的过程语言中实现其中的任何选择都可能会成问题:数据结构自身不是主动的。在这样的情形下,我们的偏好是为每个重要结构编写一个模块,为该结构提供分配和解除分配设施(这个模块也可以提供像调试打印、序列化、解序列化和遍历挂钩这样的设施)。 最后,如果追踪资源很棘手,你可以通过在动态分配的对象上实现一种引用计数方案,编写自己有限的自动垃圾回收机制。More Effective C++[Mey96]一书专设了一节讨论这一话题。检查配平 因为注重实效的程序员谁也不信任,包括我们自己,所以我们觉得,构建代码、对资源确实得到了适当释放进行实际检查,这总是一个好主意。对于大多数应用,这通常意味着为每种资源类型编写包装,并使用这些包装追踪所有的分配和解除分配。在你的代码中的特定地方,程序逻辑将要求资源处在特定的状态中:使用包装对此进行检查。 例如,一个长期运行的、对请求进行服务的程序,很可能会在其主处理循环的顶部的某个地方等待下一个请求到达。这是确定自从上次循环执行以来,资源使用未曾增长的好地方。 在一个更低、但用处并非更少的层面上,你可以投资购买能检查运行中的程序的内存泄漏情况(及其他情况)的工具。Purify(m)和Insure++(m)是两种流行的选择。相关内容:l 按合约设计,109页l 断言式编程,122页l 解耦与得墨忒耳法则,138页挑战l 尽管没有什么途径能够确保你总是释放资源,某些设计技术,如果能够始终如一地加以应用,将能对你有所帮助。在上文中我们讨论了为重要数据结构设立语义不变项可以怎样引导内存解除分配决策。考虑一下,“按合约设计”(109页)可以怎样帮助你提炼这个想法。练习22. 有些C和C++开发者故意在解除了某个指针引用的内存的分配之后,把该指针设为NULL。这为什么是个好主意? (解答在292页)23. 有些Java开发者故意在使用完某个对象之后,把该对象变量设为NULL,这为什么是个好主意? (解答在292页)相关内容:l 原型与便笺,53页l 重构,184页l 易于测试的代码,189页l 无处不在的自动化,230页l 无情的测试,237页挑战l 如果有人——比如银行柜台职员、汽车修理工或是店员——对你说蹩脚的借口,你会怎样反应?结果你会怎样想他们和他们的公司?