1.不要手动释放从函数返回的堆资源

假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来,

 class Investment { ... }; // root class of hierarchy of

 // investment types

进一步假设这个程序库通过一个工厂函数(Item 7)来给我们提供特定Investment对象:

 Investment* createInvestment(); // return ptr to dynamically allocated

 // object in the Investment hierarchy;

 // the caller must delete it

 // (parameters omitted for simplicity)

正如注释所表述的,当createInvesment返回的对象不再被使用时,调用者有责任将此对象释放掉。我们用函数f来履行这个职责:

 void f()

 {

 Investment *pInv = createInvestment(); // call factory function

 ... // use pInv

 delete pInv; // release object

 }

这个方法看上去挺好,但是在一些情况下释放从createInvestment得来的对象有可能会失败。在函数的”…”部分中有可能会出现过早的reture语句,如果这个return被执行了,那么最后的delete语句永远不会被执行到;如果createInvesment和delete在一个循环中,break和goto语句会使循环过早退出,delete也不会被执行到;最后在…中的一些语句有可能会抛出异常,如果这样的话,控制流程会再次不能执行到delete。不管delete是怎么被跳过去的,不仅会泄露Invesment对象所使用的内存,也会泄露Investment对象所拥有的任何资源。

当然,小心的编程可以防止这类错误的发生,但是你应该想到随着时间的推移代码有可能发生变化。在软件的维护过程中,一些人可能在没有完全领会这个函数的资源管理策略的情况下为其添加一个return或者continue语句。更糟糕的是,f函数的”…”部分有可能调用一个从来没有抛出异常的函数,但这个函数被“改善”后,它抛出异常了。所以依赖f来到达delete语句通常是不可行的。

2.通过对象来管理需要手动释放的资源

为了确保从createInvestment返回的资源总是被释放,我们需要将资源放到一个对象中,当离开函数f的时候,对象的析构函数会自动释放对象拥有的资源。事实上,我们已经说出了这个条款一半的内容:通过将资源放入对象中,我们可以依赖c++的析构函数自动调用机制来确保资源被释放。(另一半一会就会讲到)

2.1 使用auto_ptr来管理资源

许多资源是被动态的分配在堆上的,它们被用在一个单独的块或者函数中,当控制流离开块或者函数时,这些资源应该被释放。标准库中的auto_ptr正是为这种情况量身定做的。Auto_ptr是一个指针(智能指针)一样的对象,它的析构函数会自动为其指向的对象调用delete函数。下面演示如何使用auto_ptr来防止可能出现的资源泄露:

 void f()

 {

 std::auto_ptr<Investment> pInv(createInvestment()); // call factory

 // function

 ... // use pInv as

 // before

 } // automatically

 // delete pInv via

 // auto_ptr’s dtor

2.2 用对象管理资源的两个关键点

这个简单的例子指出了使用对象管理资源的两个关键点:

  • 获取资源后应该立即将其转交给资源管理对象。从上面的例子看出,使用createInvestment返回的资源来初始化对其进行管理的auto_ptr指针。事实上,用对象来管理资源的想法通常被叫做”资源获取的时候就是初始化的时候”(Resource Acquisition Is Initialization RAII),因为将资源获取和资源管理对象的初始化放在同一个语句中是非常常见的。有时用获取的资源给资源管理对象赋值而不是初始化,但是不管哪种方法,都是在资源获取到之后马上将控制权转交给资源管理对象。
  • 资源管理对象使用它们的析构函数来确保资源被释放。因为不管控制流是怎么离开块或函数的,对象销毁的时候析构函数会被自动调用(例如当一个对象超出了作用域),资源因此能够被正确释放。释放资源时抛出异常会使问题变的棘手,这个问题在Item8中讨论了,我们不再担心这种问题。

因为 当auto_ptr被销毁时会自动delete它所指向的资源,所以有没有多个auto_ptr指向通一个对象是很重要的。如果有多个,对象会被多次delete,这就会导致出现未定义行为。为了防止这样的问题出现,auto_ptrs有一个与众不同的性质:被拷贝的指针(通过拷贝构造函数或者拷贝赋值运算符)会被置为null,进行拷贝的指针将拥有资源的所有权

 std::auto_ptr<Investment> // pInv1 points to the

 pInv1(createInvestment()); // object returned from

 // createInvestment

 std::auto_ptr<Investment> pInv2(pInv1); // pInv2 now points to the

 // object; pInv1 is now null

 pInv1 = pInv2; // now pInv1 points to the

 // object, and pInv2 is null

2.3 用shared_ptr来管理资源

奇特的拷贝行为,加上“不能有超过一个的auto_ptr指向被auto_ptr管理的资源”,这两种特性使得auto_ptrs不是管理所有动态分配资源的最好方法。举个例子,STL容器需要”正常的”拷贝行为,因此就不能将容器放入auto_ptr中。

Auto_ptr的一种替代方法是使用“引用计数的智能指针”(reference-counting smart pointer RCSP).RCSP是一种能够跟踪有多少对象指向同个一特定资源的指针,资源只有在没有指针指向的情况下才能被释放。因此,RCSP提供的行为同垃圾回收机制类似。和垃圾回收机制不同的是,RCSP不会制止循环引用(例如,两个都不被使用的对象却指向彼此,看上去在被使用一样。)

TR1的tr1::shared_ptr(看Item54)是是一个RCSP,所以你可以这么实现f:

 void f()

 {

 ...

 std::tr1::shared_ptr<Investment>

 pInv(createInvestment()); // call factory function

 ... // use pInv as before

 } // automatically delete

 // pInv via shared_ptr’s dtor

这段代码看上去同使用auto_ptr大致相同,但是拷贝shared_ptrs的行为更加自然:

 void f()

 {

 ...

 std::tr1::shared_ptr<Investment> // pInv1 points to the

 pInv1(createInvestment()); // object returned from

 // createInvestment

 std::tr1::shared_ptr<Investment> // both pInv1 and pInv2 now
pInv2(pInv1); // point to the object
pInv1 = pInv2; // ditto — nothing has
// changed
...
} // pInv1 and pInv2 are
// destroyed, and the
// object they point to is
// automatically deleted

因为拷贝tr1::shared_ptrs的工作方式是你所想要的,它们可以被用在像STL容器和其他上下文中,在这里auto_ptr的古怪的拷贝方式不再合适。

2.4 不要将auto_ptr和shared_ptr用于动态分配数组

不要被误导。这个条款不是用来介绍关于auto_ptr,tr1::shared_ptr或者其它类型的智能指针。这个条款讲述的是用对象管理资源的重要性。使用Auto_ptr和tr1::shared_ptr只是举个例子。(关于tr1::shared_ptr的更多内容,查看Item14 18和54)

Auto_ptr和tr1::shared_ptr的析构函数中使用的是delete而不是delete[]。(Item16 描述了区别)这意味着在auto_ptr或者tr1::shared_ptr中存放动态分配的数组不是一个好方法,令人遗憾的是,这种用法可以通过编译:

 std::auto_ptr<std::string> // bad idea! the wrong

 aps(new std::string[]); // delete form will be used

 std::tr1::shared_ptr<int> spi(new int[]); // same problem

你会惊奇的发现c++中没有用于动态分配数组的类似auto_ptr或者tr1::shared_ptr的东西,TR1中也没有。因为vector和string基本可以替代动态分配数组了。如果你仍然认为存在用于动态分配数组的类似于auto_ptr和tr1::shared_ptr的类是好的,可以看一下Boost(Item 55).你会非常高兴的发现boost::scoped_array和boost::shared_array类提供了你正在寻找的。

3.其他问题

这个条款中,使用对象管理资源的指导方针意味着如果你自己手动释放资源(例如使用delete而不是一个资源管理类),你的做法就是错误的。 预装的资源管理类,像auto_ptr和tr1::shared_ptr使遵守这个条款变的更加容易,但有时候当你使用一个资源的时候你会发现这些预制的类没有做到你想要的。这种情况下,你就需要编写你自己的资源管理类了。这也不是非常难的,但确实有一些微妙的地方需要你考虑。这些注意点将要在Item14和Item15种进行讨论。

最后,我必须指出createInvestment的原生指针返回类型是资源泄露的导火索,因为调用者很容易就会忘记调用delete(即使使用auto_ptr和tr1::shared_ptr来执行delete,它们仍然需要记得将createInvestment的返回值放入智能指针对象中)。对付这个问题需要调用createInvestment的修订版本,这个问题会在Item18中进行讨论。

读书笔记 effective c++ Item 13 用对象来管理资源的更多相关文章

  1. 读书笔记 effective c++ Item 12 拷贝对象的所有部分

    1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...

  2. 读书笔记 effective c++ Item 4 确保对象被使用前进行初始化

    C++在对象的初始化上是变化无常的,例如看下面的例子: int x; 在一些上下文中,x保证会被初始化成0,在其他一些情况下却不能够保证.看下面的例子: class Point { int x,y; ...

  3. 读书笔记 effective c++ Item 17 使用单独语句将new出来的对象放入智能指针

    1. 可能会出现资源泄漏的一种用法 假设我们有一个获取进程优先权的函数,还有一个在动态分类的Widget对象上根据进程优先权进行一些操作的函数: int priority(); void proces ...

  4. 读书笔记 effective c++ Item 29 为异常安全的代码而努力

    异常安全在某种意义上来说就像怀孕...但是稍微想一想.在没有求婚之前我们不能真正的讨论生殖问题. 假设我们有一个表示GUI菜单的类,这个GUI菜单有背景图片.这个类将被使用在多线程环境中,所以需要mu ...

  5. 读书笔记 effective c++ Item 49 理解new-handler的行为

    1. new-handler介绍 当操作符new不能满足内存分配请求的时候,它就会抛出异常.很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做.你仍然会看到这种旧行为,但是我会把关于它的讨 ...

  6. 读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)

    假设你正在操作一个Rectangle类.每个矩形可以通过左上角的点和右下角的点来表示.为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放 ...

  7. 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库

    1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...

  8. 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎

    1. 自己实现一个资源管理类 Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如 ...

  9. 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问

    1.为什么需要访问资源管理类中的原生资源  资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...

随机推荐

  1. python 错误AttributeError: 'module' object has no attribute 'AF_INET'

    写了一个简单的python socket的程序.运行时,报错如下 原因:文件的命名与Python的function的命名冲突 修改名称后,发现还是无法运行,检查目录下面是否有 这样子的一个文件,删除即 ...

  2. BZOJ 1455: 罗马游戏 [可并堆]

    1455: 罗马游戏 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1715  Solved: 718[Submit][Status][Discuss] ...

  3. Linux文件编辑之sed命令

    文件编辑之sed命令 sed是一种流编辑器,它是文本处理中非常重要的工具,能够完美配合正则表达式使用,功能不同凡响.处理时,把当前处理的行存储在临时缓冲区中,称为模式空间 (pattern space ...

  4. 从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(转载)

    从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理 1.前言   在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或经纬度)的换算方法, ...

  5. 使用YUIDoc生成JS文档

    其实YUIDoc主页已经写的比较清晰了,但有一些概念和细节再点出一些注意的地方. 目前最新的YUIDoc使用nodejs进行开发安装和使用都非常的方便. 我们只需要将我们的代码加上必要的注释,便可以很 ...

  6. Mysql中如何创建、删除授权用户

    在mysql数据库下使用create user创建新用户,例如: 新创建后的用户没有任何授权.使用grant命令授权xushouwei访问数据库databaseweb下的所有表,密码为xsw12345 ...

  7. 封装 INI 文件读写函数

    delphi读写ini文件实例 //--两个过程,主要实现:窗体关闭的时候,文件保存界面信息:窗体创建的时候,程序读取文件文件保存的信息. //--首先要uses IniFiles(单元) //--窗 ...

  8. DELPHI中多线程知识【转】

    本文的内容取自网络,并重新加以整理,在此留存仅仅是方便自己学习和查阅.所有代码均亲自测试 delphi7下测试有效.图片均为自己制作. 多线程应该是编程工作者的基础技能, 但这个基础我从来没学过,所以 ...

  9. 给 Memo 排序的函数

    本例效果图: 代码文件: unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes, Graphics, ...

  10. HTML5 + AJAX ( 原生JavaScript ) 异步多文件上传

    这是在上篇 HTML5 + AJAX ( jQuery版本 ) 文件上传带进度条 的修改版本.后台代码不变就可以接着使用,但是脚本不再使用jQuery了,改为原生的 JavaScript 代码,所以我 ...