More effective c++ 条款10(上)

原创 2001年10月10日 18:26:00

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

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

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

通讯录的每个条目都有姓名数据,所以你需要带有参数的构造函数(参见条款3),不过其它内容(地址、图像和声音的文件名)都是可选的。注意应该使用链表类(list)存储电话号码,这个类是标准C++类库(STL)中的一个容器类(container classes)。(参见Effective C++条款49 和本书条款35)

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

构造函数把指针theImage和theAudioClip初始化为空,然后如果其对应的构造函数参数不是空就让这些指针指向真实的对象。析构函数负责删除这些指针,确保BookEntry对象不会发生资源泄漏。因为C++确保删除空指针是安全的,所以BookEntry的析构函数在删除指针前不需要检测这些指针是否指向了某些对象。

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

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

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

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

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

并且在构造b的过程中,一个异常被抛出,b的析构函数不会被调用。而且如果你试图采取主动手段处理异常情况,即当异常发生时调用delete,如下所示:

你会发现在BookEntry构造函数里为Image分配的内存仍旧被丢失了,这是因为如果new操作没有成功完成,程序不会对pb进行赋值操作。如果BookEntry的构造函数抛出一个异常,pb将是一个空值,所以在catch块中删除它除了让你自己感觉良好以外没有任何作用。用灵巧指针(smart pointer)类auto_ptr<BookEntry>参见条款9代替raw BookEntry*也不会也什么作用因为new操作成功完成前,也没有对pb进行赋值操作。

C++拒绝为没有完成构造操作的对象调用析构函数是有一些原因的,而不是故意为你制造困难。原因是:在很多情况下这么做是没有意义的,甚至是有害的。如果为没有完成构造操作的对象调用析构函数,析构函数如何去做呢?仅有的办法是在每个对象里加入一些字节来指示构造函数执行了多少步?然后让析构函数检测这些字节并判断该执行哪些操作。这样的记录会减慢析构函数的运行速度,并使得对象的尺寸变大。C++避免了这种开销,但是代价是不能自动地删除被部分构造的对象。(类似这种在程序行为与效率这间进行折衷处理的例子还可以参见Effective C++条款13

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

不用为BookEntry中的非指针数据成员操心,在类的构造函数被调用之前数据成员就被自动地初始化。所以如果BookEntry构造函数体开始执行,对象的theName, theAddress thePhones数据成员已经被完全构造好了。这些数据可以被看做是完全构造的对象,所以它们将被自动释放,不用你介入操作。当然如果这些对象的构造函数调用可能会抛出异常的函数,那么哪些构造函数必须去考虑捕获异常,在允许它们继续传递之前完成必需的清除操作。

《More Effective C++》条款27:如何让类对象只在栈(堆)上分配空间?

昨天一个同学去网易面试C++研发,问到了这么一个问题:如何限制一个类对象只在栈(堆)上分配空间? 一般情况下,编写一个类,是可以在栈或者堆分配空间。但有些时候,你想编写一个只能在栈或者只能在堆上面分...
  • hxz_qlh
  • hxz_qlh
  • 2013年10月26日 21:27
  • 6092

Effective C++——条款10条,条款11和条款12(第2章)

条款10:    令operator=返回一个reference to *this Have assignment operators return a reference to *this ...
  • yiranant
  • yiranant
  • 2015年08月29日 23:35
  • 606

Effective C++ 条款10

令operator=返回一个reference to *this将operator=返回一个reference是为了什么呢?答案很简单,就是为了实现连锁形式。什么是连锁形式,如int x,y,z;x=...
  • u011058765
  • u011058765
  • 2015年06月23日 08:23
  • 648

Effective Modern C++ 条款9补完 理解模板类型推断

http://blog.csdn.net/big_yellow_duck/article/details/52224068 看大黄鸭的《Effective Modern C++》翻译时,第9款最后有一...
  • fesdobat
  • fesdobat
  • 2017年02月05日 12:56
  • 216

Effective C++ 条款9

绝不在构造和析构过程中调用virtual函数本节有个核心的知识点就是在构造函数和析构函数中,virtual函数失去多态性。 试想一下,假设此时在构造函数和析构函数中,virtual函数没...
  • u011058765
  • u011058765
  • 2015年06月22日 11:15
  • 552

Effective Modern C++ 条款7 创建对象时区分( )和{ }

Effective Modern C++ 条款7
  • big_yellow_duck
  • big_yellow_duck
  • 2016年08月13日 22:09
  • 846

《Effective C++》:条款28-条款29

条款28避免返回handles指向对象内部成分:指的是不能返回对象内部数据/函数的引用、指针等。 条款29为异常安全而努力是值得的:指的是要有异常处理机制,避免发生异常时造成资源泄露等问题。...
  • KangRoger
  • KangRoger
  • 2015年02月19日 19:47
  • 1394

Effective C++ 条款2

尽量以const、enum、inline替换#define首先,大家要明白一个道理。#define是什么,有什么作用。很简单,大家都知道#define实现宏定义,如下代码:#define Flag 1...
  • u011058765
  • u011058765
  • 2015年06月19日 12:06
  • 499

《Effective C++》:条款41-条款42

条款41了解隐式接口和编译期多态 条款42了解typename的双重意义条款
  • KangRoger
  • KangRoger
  • 2015年03月10日 22:13
  • 1243

《Effective C++》:条款44-条款45

条款44将与参数无关的代码抽离templates 条款45运用成员函数模板接受所有兼容类型...
  • KangRoger
  • KangRoger
  • 2015年03月12日 22:01
  • 1509
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:More effective c++ 条款10(上)
举报原因:
原因补充:

(最多只允许输入30个字)