首页 | 互联网 | IT动态 | IT培训 | Cisco | Windows | Linux | Java | .Net | Oracle | 软件测试 | C/C++ | 嵌入式开发 | 存储世界 | 服务器
网络设备 | IDC | 安全 | 求职招聘 | 数字网校 | 网页设计 | 平面设计 | 技术专题 | 电子书下载 | 教学视频 | 源码下载 | 搜索 | 博客 | 论坛
首页 | JAVA | C# | VB | VB.NET | C/C++ | delphi | 工程管理 | 其他语言 | 论坛
各大城市软件开发培训、软件人才免费咨询热线:400-700-5807
 您现在的位置: 中国IT实验室 >> 桌面开发 >> C# >> 正文
More Effective C++:防止资源泄漏
来源:ChinaItLab 作者:佚名 时间:2007-6-6


 你可能已经注意到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的类)能化繁为简。它不仅把令人不好理解的代码隐藏起来,而且使得程序在面对异常的情况下也能保持正常运行。

上一页  [1] [2] 

【责编:runlz】
中国IT教育热线咨询
相关文章
c#.net常用函数列表
推荐文章

 精彩友情推荐
·Asp源码 PHP源码
·CGI源码 JSP源码
·建站书籍教程
·服务器软件 .net源码
·建站工具软件
·IDC资讯大全
·机房品质万里行
·IDC托管必备知识
·全国IDC报价
·网站推广优化
最新更新 推荐文章
·框架:J2EE WEB应用架构分析…03-13
·几种VC++数据库开发技术的相对比…03-13
·利用C#实现标注式消息提示窗口03-13
·用C#创建COM对象03-13
·Visual C#多线程参数传递浅析…03-13
·Visual C#多线程参数传递浅析…03-13
·基于HOOK和MMF的Win密码渗透技术11-15
·Visual C++设计超强仿QQ自动伸缩…11-15
·Java SE 6.0实现高质量桌面集成开…11-15
·史玉柱东山再起幕后高人11-15
·用C#创建COM对象09-06
·IT管理十大失误及其对策08-30
·VC中利用MFC设计绘图程序初步08-23
·JAVA中对象创建和初始化过程08-23
·C语言中的位域的使用08-09
·浅谈Java桌面应用程序开发08-09
·C#的前途如何?08-02
·几种VC++数据库开发技术的相对比较07-12
·用Visual C#实现网络封包监视…07-12
·VB.NET中的TextBox控件详解07-12
·VB.NET实现PC与掌上电脑PPC的双向通信07-05