动态网站制作指南 [  QQ表情  ]
[ 投票调查 ]
[ 企业邮箱 ]
[ 网站空间 ]
网络编程 | 站长之家 | 网页制作 | 图形图象 | 操作系统 | 冲浪宝典 | 软件教学 | 网络办公 | 邮件系统 | 网络安全 | 认证考试 | 系统进程
ASP源码 | .Net源码 | PHP源码 | JSP源码 | JAVA源码 | CGI源码 | VB源码 | C++源码 | Delphi源码 | PB源码 | VF源码 | 汇编 | 服务器
Firefox | IE | Maxthon | 迅雷 | 电驴 | BitComet | FlashGet | QQ | QQ空间 | Vista | 输入法 | Ghost | Word | Excel | wps | Powerpoint
asp | .net | php | jsp | Sql | c# | Ajax | xml | Dreamweaver | FrontPages | Javascript | css | photoshop | fireworks | Flash | Cad | Discuz!
当前位置 > 网站建设学院 > 网络编程 > C/C++教程
Tag:注入,存储过程,分页,安全,优化,xmlhttp,fso,jmail,application,session,防盗链,stream,无组件,组件,md5,乱码,缓存,加密,验证码,算法,cookies,ubb,正则表达式,水印,索引,日志,压缩,base64,url重写,上传,控件,Web.config,JDBC,函数,内存,PDF,迁移,结构,破解,编译,配置,进程,分词,IIS,Apache,Tomcat,phpmyadmin,Gzip,触发器,socket
网络编程:ASP教程,ASP.NET教程,PHP教程,JSP教程,C#教程,数据库,XML教程,Ajax,Java,Perl,Shell,VB教程,Delphi,C/C++教程,软件工程,J2EE/J2ME,移动开发
文章搜索服务
邮件订阅
输入你的邮件地址,
你将不会错过任何关于:
[ C/C++教程 ]的信息

本月文章推荐
.C语言:超越C++下一代C++ —C++/.
.再谈C语言中数组和指针之间的互操.
.C趣味程序百例(15).
.C语言库函数(S类字母).
.想成为嵌入式程序员应知道的0x10.
.文本模式模仿磁盘扫描外观.
.回文数的形成.
.指针与函数的关系.
.数据结构学习C++——树(总结).
.从初学者到编程高手,几种必学的.
.Visual FoxPro 9 集成开发环境新.
.用OLE操作Excel(Wangda补充).
.又一个贪吃蛇.
.C++ 对象的初始化.
.有趣的分形学Mandlbrot集图形的一.
.实战JBOSS――教你写第一个EJB.
.C++类的继承与多重继承的访问控制.
.用VSTS代码验证工具捕捉C/C++错误.
.Delphi中带缓存的数据更新技术.
.在C++程序中添加逻辑流程控制.

More Effective C++:防止资源泄漏

发表日期:2008-3-8 |



  假如你正在开发一个具有多媒体功能的通讯录程序。这个通讯录除了能存储通常的文字信息如姓名、地址、电话号码外,还能存储照片和声音(可以给出他们名字的正确发音)。

  为了实现这个通信录,你可以这样设计:

class Image { // 用于图像数据
 public:
  Image(const string& imageDataFileName);
  ...
};

class AudioClip { // 用于声音数据
 public:
  AudioClip(const string& audioDataFileName);
  ...
};

class PhoneNumber { ... }; // 用于存储电话号码
class BookEntry { // 通讯录中的条目
 public:
  BookEntry(const string& name,
  const string& address = "",
  const string& imageFileName = "",
  const string& audioClipFileName = "");
  ~BookEntry();
  // 通过这个函数加入电话号码
  void addPhoneNumber(const PhoneNumber& number);
  ...
 private:
  string theName; // 人的姓名
  string theAddress; // 他们的地址
  list thePhones; // 他的电话号码
  Image *theImage; // 他们的图像
  AudioClip *theAudioClip; // 他们的一段声音片段
};
  通讯录的每个条目都有姓名数据,所以你需要带有参数的构造函数(参见条款3),不过其它内容(地址、图像和声音的文件名)都是可选的。注重应该使用链表类(list)存储电话号码,这个类是标准C++类库(STL)中的一个容器类(container classes)。(参见Effective C++条款49 和本书条款35)

  编写BookEntry 构造函数和析构函数,有一个简单的方法是:

BookEntry::BookEntry(const string& name,const string& address,
 const string& imageFileName,
 Const string& audioClipFileName)
 : theName(name), theAddress(address),
 theImage(0), theAudioClip(0)
 {
  if (imageFileName != "") {
   theImage = new Image(imageFileName);
  }
  if (audioClipFileName != "") {
   theAudioClip = new AudioClip(audioClipFileName);
  }
 }
 BookEntry::~BookEntry()
 {
  delete theImage;
  delete theAudioClip;
 }
  构造函数把指针theImage和theAudioClip初始化为空,然后假如其对应的构造函数参数不是空,就让这些指针指向真实的对象。析构函数负责删除这些指针,确保BookEntry对象不会发生资源泄漏。因为C++确保删除空指针是安全的,所以BookEntry的析构函数在删除指针前不需要检测这些指针是否指向了某些对象。

  看上去似乎一切良好,在正常情况下确实不错,但是在非正常情况下(例如在有异常发生的情况下)它们恐怕就不会良好了。

  请想一下假如BookEntry的构造函数正在执行中,一个异常被抛出,会发生什么情况呢?:

if (audioClipFileName != "") {
 theAudioClip = new AudioClip(audioClipFileName);
}
  一个异常被抛出,可以是因为operator new(参见条款8)不能给AudioClip分配足够的内存,也可以因为AudioClip的构造函数自己抛出一个异常。不论什么原因,假如在BookEntry构造函数内抛出异常,这个异常将传递到建立BookEntry对象的地方(在构造函数体的外面。 译者注)。

  现在假设建立theAudioClip对象建立时,一个异常被抛出(而且传递程序控制权到BookEntry构造函数的外面),那么谁来负责删除theImage已经指向的对象呢?答案显然应该是由BookEntry来做,但是这个想当然的答案是错的。BookEntry根本不会被调用,永远不会。

  C++仅仅能删除被完全构造的对象(fully contrUCted objects), 只有一个对象的构造函数完全运行完毕,这个对象才能被完全地构造。所以假如一个BookEntry对象b做为局部对象建立,如下:

void testBookEntryClass()
{
 BookEntry b("Addison-Wesley Publishing Company","One Jacob Way, Reading, MA 01867");
 ...
}
  并且在构造b的过程中,一个异常被抛出,b的析构函数不会被调用。而且假如你试图采取主动手段处理异常情况,即当异常发生时调用delete,如下所示:


void testBookEntryClass()
{
 BookEntry *pb = 0;
 try {
  pb = new BookEntry("Addison-Wesley Publishing Company","One Jacob Way, Reading, MA 01867");
  ...
 }
 catch (...) { // 捕捉所有异常
  delete pb; // 删除pb,当抛出异常时
 throw; // 传递异常给调用者
}

delete pb; // 正常删除pb
}
  你会发现在BookEntry构造函数里为Image分配的内存仍然被丢失了,这是因为假如new操作没有成功完成,程序不会对pb进行赋值操作。假如BookEntry的构造函数抛出一个异常,pb将是一个空值,所以在catch块中删除它除了让你自己感觉良好以外没有任何作用。用灵巧指针(smart pointer)类auto_ptr(参见条款9)代替raw BookEntry*也不会也什么作用,因为new操作成功完成前,也没有对pb进行赋值操作。
  C++拒绝为没有完成构造操作的对象调用析构函数是有一些原因的,而不是故意为你制造困难。原因是:在很多情况下这么做是没有意义的,甚至是有害的。假如为没有完成构造操作的对象调用析构函数,析构函数如何去做呢?仅有的办法是在每个对象里加入一些字节来指示构造函数执行了多少步?然后让析构函数检测这些字节并判定该执行哪些操作。这样的记录会减慢析构函数的运行速度,并使得对象的尺寸变大。C++避免了这种开销,但是代价是不能自动地删除被部分构造的对象。(类似这种在程序行为与效率这间进行折衷处理的例子还可以参见Effective C++条款13)

  因为当对象在构造中抛出异常后C++不负责清除对象,所以你必须重新设计你的构造函数以让它们自己清除。经常用的方法是捕捉所有的异常,然后执行一些清除代码,最后再重新抛出异常让它继续转递。如下所示,在BookEntry构造函数中使用这个方法:

BookEntry::BookEntry(const string& name,
 const string& address,
 const string& imageFileName,
 const string& audioClipFileName)
 : theName(name), theAddress(address),
 theImage(0), theAudioClip(0)
{
 try { // 这try block是新加入的
  if (imageFileName != "") {
   theImage = new Image(imageFileName);
  }
 if (audioClipFileName != "") {
  theAudioClip = new AudioClip(audioClipFileName);
 }
}
catch (...) { // 捕捉所有异常
 delete theImage; // 完成必要的清除代码
 delete theAudioClip;
 throw; // 继续传递异常
}
}
  不用为BookEntry中的非指针数据成员操心,在类的构造函数被调用之前数据成员就被自动地初始化。所以假如BookEntry构造函数体开始执行,对象的theName, theAddress 和 thePhones数据成员已经被完全构造好了。这些数据可以被看做是完全构造的对象,所以它们将被自动释放,不用你介入操作。当然假如这些对象的构造函数调用可能会抛出异常的函数,那么哪些构造函数必须去考虑捕捉异常,在答应它们继续传递之前完成必需的清除操作。

  你可能已经注重到BookEntry构造函数的catch块中的语句与在BookEntry的析构函数的语句几乎一样。这里的代码重复是绝对不可容忍的,所以最好的方法是把通用代码移入一个私有helper function中,让构造函数与析构函数都调用它。

class BookEntry {
 public:
  ... // 同上
 private:
  ...
  void cleanup(); // 通用清除代码
};

void BookEntry::cleanup()
{
 delete theImage;
 delete theAudioClip;
}

BookEntry::BookEntry(const string& name,const string& address,
  const string& imageFileName,
  const string& audioClipFileName)
  : theName(name), theAddress(address),
  theImage(0), theAudioClip(0)
{
 try {
  ... // 同上
}

catch (...) {
 cleanup(); // 释放资源
 throw; // 传递异常
}
}

BookEntry::~BookEntry()
{
 cleanup();
}

  这就行了,但是它没有考虑到下面这种情况。假设我们略微改动一下设计,让theImage 和theAudioClip是常量(constant)指针类型:

class BookEntry {
 public:
  ... // 同上
 private:
  ...
  Image * const theImage; // 指针现在是
  AudioClip * const theAudioClip; // const类型
};

  必须通过BookEntry构造函数的成员初始化表来初始化这样的指针,因为再也没有其它地方可以给const指针赋值。通常会这样初始化theImage和theAudioClip:


// 一个可能在异常抛出时导致资源泄漏的实现方法

BookEntry::BookEntry(const string& name,const string& address,const string& imageFileName,
 const string& audioClipFileName)
 : theName(name), theAddress(address),
 theImage(imageFileName != ""
 ? new Image(imageFileName)
 : 0),
 theAudioClip(audioClipFileName != ""
 ? new AudioClip(audioClipFileName)
 : 0)
 {}

  这样做导致我们原先一直想避免的问题重新出现:假如theAudioClip初始化时一个异常被抛出,theImage所指的对象不会被释放。而且我们不能通过在构造函数中增加try和catch 语句来解决问题,因为try和catch是语句,而成员初始化表仅答应有表达式(这就是为什么我们必须在 theImage 和 theAudioClip的初始化中使用?:以代替if-then-else的原因)。

  无论如何,在异常传递之前完成清除工作的唯一的方法就是捕捉这些异常,所以假如我们不能在成员初始化表中放入try和catch语句,我们把它们移到其它地方。一种可能是在私有成员函数中,用这些函数返回指针,指向初始化过的theImage 和 theAudioClip对象。

class BookEntry {
 public:
  ... // 同上
 private:
  ... // 数据成员同上
  Image * initImage(const string& imageFileName);
  AudioClip * initAudioClip(const string&
  audioClipFileName);
};
BookEntry::BookEntry(const string& name,const string& address,const string& imageFileName,
const string& audioClipFileName): theName(name), theAddress(address),
theImage(initImage(imageFileName)),
theAudioClip(initAudioClip(audioClipFileName)){}

// theImage 被首先初始化,所以即使这个初始化失败也
// 不用担心资源泄漏,这个函数不用进行异常处理。

Image * BookEntry::initImage(const string& imageFileName)
{
 if (imageFileName != "") return new Image(imageFileName);
 else return 0;
}

// theAudioClip被第二个初始化, 所以假如在theAudioClip
// 初始化过程中抛出异常,它必须确保theImage的资源被释放。
// 因此这个函数使用try...catch 。

AudioClip * BookEntry::initAudioClip(const string& audioClipFileName)
{
 try {
  if (audioClipFileName != "") {
   return new AudioClip(audioClipFileName);
  }
  else return 0;
 }
 catch (...) {
  delete theImage;
 throw;
 }
}
  上面的程序的确不错,也解决了令我们头疼不已的问题。不过也有缺点,在原则上应该属于构造函数的代码却分散在几个函数里,这令我们很难维护。

  更好的解决方法是采用条款9的建议,把theImage 和 theAudioClip指向的对象做为一个资源,被一些局部对象治理。这个解决方法建立在这样一个事实基础上:theImage 和theAudioClip是两个指针,指向动态分配的对象,因此当指针消失的时候,这些对象应该被删除。auto_ptr类就是基于这个目的而设计的。(参见条款9)因此我们把theImage 和 theAudioClip raw指针类型改成对应的auto_ptr类型。

class BookEntry {
public:
 ... // 同上
private:
 ...
 const auto_ptr theImage; // 它们现在是
 const auto_ptr theAudioClip; // auto_ptr对象
};
  这样做使得BookEntry的构造函数即使在存在异常的情况下也能做到不泄漏资源,而且让我们能够使用成员初始化表来初始化theImage 和 theAudioClip,如下所示:

BookEntry::BookEntry(const string& name,const string& address,const string& imageFileName,const string& audioClipFileName): theName(name), theAddress(address),theImage(imageFileName != ""? new Image(imageFileName)
: 0),theAudioClip(audioClipFileName != ""? new AudioClip(audioClipFileName): 0){}
  在这里,假如在初始化theAudioClip时抛出异常,theImage已经是一个被完全构造的对象,所以它能被自动删除掉,就象theName, theAddress和thePhones一样。而且因为theImage 和 theAudioClip现在是包含在BookEntry中的对象,当BookEntry被删除时它们能被自动地删除。因此不需要手工删除它们所指向的对象。可以这样简化BookEntry的析构函数:


BookEntry::~BookEntry()
{} // nothing to do!
  这表示你能完全去掉BookEntry的析构函数。

  综上所述,假如你用对应的auto_ptr对象替代指针成员变量,就可以防止构造函数在存在异常时发生资源泄漏,你也不用手工在析构函数中释放资源,并且你还能象以前使用非const指针一样使用const指针,给其赋值。

  在对象构造中,处理各种抛出异常的可能,是一个棘手的问题,但是auto_ptr(或者类似于auto_ptr的类)能化繁为简。它不仅把令人不好理解的代码隐藏起来,而且使得程序在面对异常的情况下也能保持正常运行。
上一篇:VC vs CBuilder 人气:411
下一篇:C++ 中不规则窗体的快速显示 人气:419
浏览全部C/C++的内容 Dreamweaver插件下载 常用网页广告代码全集
  最新网站源码 最新软件下载
2008-10-12 team论坛 v2.0.4 bulid 080916 A
2008-10-12 Roclog v3.1.6
2008-10-12 SupeV v1.0.1 简体中文 GBK
2008-10-12 NetCMS v1.6.0.1010 正式版
2008-10-12 PHP考试系统PPFrame v1.2.7
2008-10-12 LPAS个人相册 v1.6.3
2008-10-12 快问仿百度知道系统 动态-静态-互
2008-10-12 方卡广告防点击系统 V1.0 GB2312
2008-10-12 泡菜内容管理系统[PCMS] v1.0 Bu
2008-10-11 联系人分组工具 v1.1 中文破解版
2008-10-11 FaceMelter变脸 v2.0 汉化破解版
2008-10-11 PathTracker道路跟踪仪 v1.2 破解
2008-10-11 Rooms手机聊天室 v0.6.7 破解版
2008-10-11 RemoteDesktop远程桌面 v1.0 破解
2008-10-11 ProRemote远程调音台 v1.0.1 破解
2008-10-11 PicShare照片共享 v1.0.0 破解版
2008-10-11 Photogene照片编辑器 v1.5 汉化破
2008-10-11 WriteRoom共享文档 v1.0 破解版
  发表评论
姓 名: 验证码:
内 容:
站长工具:网站收录查询 | Google PR查询 | ALEXA排名查询 | CSS在线编辑器 | 广告代码 | js/vbs加密 | md5加密 | 进制转换 | UTF-8 转换工具 | Html转换js | Html转换asp | Html转换php | Html转换perl
实用工具:汉字翻译拼音 | 拼音字典 | 符号对照表 | 个税计算 | 实时汇率查询换算 | 经典小工具 | 汉字简繁转换 | 普通单位换算 | 公制单位换算 | 生辰老黄历 | 国内电话区号 | 国家代码与域名缩写 | 文字加密解密 | 健康查询 | 万年历 | 汉字横竖排版 | 手机号码查询 | 计算器 | ip搜索
业务联系 | 广告刊登 | 频道合作 | 投稿荐稿 | 联系方式 | 加入收藏 | RSS订阅
Copyright © 2000-2008 www.knowsky.com All rights reserved | 网络实名:动态网站制作指南 | 沪ICP备05001343号
ホームページ制作 不動産検索システム 求人情報
防水工事·改修工事 フットサル大会 探偵
SEO対策 中国語教室 ホームページ作成