博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
More effective c++ 条款10(上) (转)
阅读量:2501 次
发布时间:2019-05-11

本文共 4014 字,大约阅读时间需要 13 分钟。

More effective c++ 条款10(上) (转)[@more@]

条款10:在构造中防止资源泄漏(上):namespace prefix = o ns = "urn:schemas--com::office" />

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

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

class Image {  // 用于图像数据

 

public:

 

  Image(const string& imageDataFileName);

 

  ...

 

};

 

 

 

class AudioCl{   // 用于声音数据

 

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 s), 只有一个对象的构造函数完全运行完毕,这个对象才能被完全地构造。所以如果一个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
数据成员已经被完全构造好了。这些数据可以被看做是完全构造的对象,所以它们将被自动释放,不用你介入操作。当然如果这些对象的构造函数调用可能会抛出异常的函数,那么哪些构造函数必须去考虑捕获异常,在允许它们继续传递之前完成必需的清除操作。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10748419/viewspace-1007834/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/10748419/viewspace-1007834/

你可能感兴趣的文章
Linux(SUSE 12)安装jboss4并实现远程访问
查看>>
Neutron在给虚拟机分配网络时,底层是如何实现的?
查看>>
netfilter/iptables全攻略
查看>>
Overlay之VXLAN架构
查看>>
Eclipse : An error occurred while filtering resources(Maven错误提示)
查看>>
在eclipse上用tomcat部署项目404解决方案
查看>>
web.xml 配置中classpath: 与classpath*:的区别
查看>>
suse如何修改ssh端口为2222?
查看>>
详细理解“>/dev/null 2>&1”
查看>>
suse如何创建定时任务?
查看>>
suse搭建ftp服务器方法
查看>>
centos虚拟机设置共享文件夹并通过我的电脑访问[增加smbd端口修改]
查看>>
文件拷贝(IFileOperation::CopyItem)
查看>>
MapReduce的 Speculative Execution机制
查看>>
大数据学习之路------借助HDP SANDBOX开始学习
查看>>
Hadoop基础学习:基于Hortonworks HDP
查看>>
为什么linux安装程序 都要放到/usr/local目录下
查看>>
Hive安装前扫盲之Derby和Metastore
查看>>
永久修改PATH环境变量的几种办法
查看>>
大数据学习之HDP SANDBOX开始学习
查看>>