Effective STL 条款7

原创 2004年07月23日 13:27:00

条款7.使用包含由new产生的指针容器时,切记在容器销毁前delete指针

容器在STL中被认为是智能的。它们支持向前和向后的迭代器;它们能告诉你它所保存的对象类型(通过typedef value_type);在插入和删除过程中它们进行了良好的内存管理;它们将报告自己包含了多少对象和自己最多能包含多少对象(分别通过sizemax_size取得);并且,当容器销毁时,它自动销毁每个被包含的对象。

拥有如此聪明的容器,许多程序员自己不再担心清理问题。他们认为容器会为他们操心。多数情况下,他们正确,但是当容器包括由new生产对象指针时,他们就不是太正确。毫无疑问,指针容器在销毁时,会销毁它所包容的每一个元素,但是指针的“析构函数”只是一个空操作。它不会调用delete

结果是,以下代码直接导致内存资源泄漏:

void doSomething()<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

{

vector<Widget*> vwp;

for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)

vwp.push_back(new Widget);

… // use vwp

} //Widgets are leaked here!

当离开vwp的作用范围时,vwp的每一个元素都会被销毁,但是这并不改变new所产生的对象没有被delete这个事实。这个删除动作是程序员的责任,而不是vector的。这其实是一个功能,因为只有程序员才知道指针是否需要删除。

通常,程序员希望它们那样(删除指针)。在那种情况(上例)中,使它发生其实很简单。

void doSomething()

{

vector<Widget*> vwp;

… // as before

for (vector<Widget*>::iterator i = vwp.begin();i != vwp.end(),++i) {

delete *i;

}

这能行,如果你不是十分在意它只是“能行”。问题之一是新的for循环做了很多for_each做的事,但它不像for_each一样清析。另一个问题是代码不是异常安全。如果一个异常在vwp填上指针之后,而这些指针还没有删除之前被抛出。资源泄漏再次出现。幸运的是两个问题都可以克服。

修改for_each类似的代码以使用真正的for_each,需要将delete操作置于(仿)函数对象中。这像一个儿童游戏,假设你有一个喜欢与STL一起玩游戏的小孩。

template<typename T>

struct DeleteObject:                  // Item 40 describes why

public unary_function<const T*, void> { //this inheritance is here

 

void operator()(const T* ptr) const

delete ptr;

}

};

现在你可以这样做:

void doSomething()

{

… // as before

for_each(vwp.begin(), vwp.end(), DeleteObject<Widget>);

}

不太走运,它要求指明DeleteObject删除的对象类型(这里是:Widget )。令人讨厌,vwp是一个vector<Widget*>,因此DeteleObject删除的当然是Widget指针!这种冗余不只是令人讨厌,因为它可能导致难以检查的bug出现。试想一下,例如,有人故意决定要从string继承:

class SpecialString: public string { ...};

这是一种危险的产物,因为string与其它标准STL容器一样,没有virtual析构函数。公有继承一个没有虚析构函数的对象是C++一个主要的误区(a major C++ no-no(近一步的细节,在任何一个很好的C++书中都有讨论。在Effective C++中,它被放在Item 14)。不论如何,有人如此作了。考虑一下以下代码的行为:

void doSomething()

{

deque<SpecialString*> dssp;

for_each( dssp.begin(), dssp.end(), // undefined behavior! Deletion

DeleteObject<string>()); //of a derived object via a base

} // class pointer where there is

//no virtual destructor

注意dssp声明为保存SpecialString的指针,但是for_each循环的作者告诉DeleteObject,它准备删除string的指针。很容易发现什么样的错误会发生。SpecialString无疑在很大程度上表现为string。因此有人会忘记它的用户,他们会不记得他们使用的是SpecialString而不是string

可以排除这个错误(也可以减少DeleteObject用户敲打键盘的次数)使用编译器推绎出传给DeleteObject::operator()的指针类型。所有工作只是把模板从DeleteObject类移到operator()上。

struct DeleteObject { // templatization and base

// class removed here

template<typename T> II templatization added here

void operator()(const T* ptr) const

{

delete ptr;

}

}

编译器知道传递给DeleteObject::operator()的指针类型,因此它将自动为该类型指针生成一个operator的实例。这种类型推绎的产生,取决于我们放弃DeleteObject的可适应性。考虑一下DeleteObject被设计为如何使用,就很难找出可能发生问题的地方。

使用这一新版的DeleteObjectSpecialString客户的代码看起来像这样:

void doSomething()

{

deque<SpecialString*> dssp;

for_each( dssp.begin(), dssp.end(),

DeleteObject ()); // ah! well-defined behavior!

}

直接而且类型安全,就像我们所喜欢的那样。

但是它还不是异常安全。如果SpecialString生产了,但还没有调用for_each,一个异常被抛出,泄漏将出现。这个问题可以用很多方法解决,但最简单的也许是使用智能指针容器取代指针容器,通常使用一个引用记数的智能指针(如果不熟悉智能指针的概念,可以在中高级C++读物中找到。在More Effective C++中,这些材料在Item 28。)

STL本身并不包括引用记数的智能指针,编写一个好的-所有情况下都正确-太富有技巧,因此除非真的需要,并不需要这样做。我(作者)1996年在More Effective C++发布了了一个引用记数的智能指针,尽管它基于一些确定的智能指针实现,而且在发布前由多位有经验的开发者讨论过,但是这些年还是有一堆准确的Bug被发现。很多引用记数的智能指针可能失败的微妙情况被说明。(细节在More Effective C++勘误中讨论)

幸运地,几乎不需要自己写一个智能指针,因为已验正的实现并不难找。在Boost库(参考条款50)中就有一个这样的share_ptr。使用Boostshare_ptr,本条款最初的例子可以重写为:

void doSomething()

{

typedef boost::shared_ ptr<Widget> SPW; //SPW = "shared_ptr

// to Widget"

vector<SPW> vwp;

for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)

vwp.push_back(SPW new Widget);   // create a SPW from a

// Widget*, then do a

//push_back on it

                                 // use vwp

}   // no Widgets are leaked here, not

// even if an exception is thrown

//in the code above

千万不能被auto_ptr愚弄了,不要认为创建auto_ptr的容器,指针会被自动删除。这是可怕是想法,它是如此危险,我准备用整个条款8来说明你不应使用它。

应记住的是STL容器是智能的,但它不足以知道是否要删除它包含的指针。为了避免资源泄漏,使用指针容器时应删除指针。你需要使用智能指针或在容器销毁前手工删除每一个指针。

最后,一个类似于DeleteObject的结构可以方便地避免使用指针容器时的资源泄漏,这也许会使你联想起,也许可能创建一个类似的DeleteArray,避免使用数组指针容器时的资源泄漏。当然,这是可能的,但是是否明智就是另一个问题了。条款13解释了为什么动态申请数组总是不如vectorstring对象。所以在你坐下来写DeleteArray之前,请先看一看条款13。如果幸运,DeleteArray的时代将永远不会到来。

effective stl 第19条:理解相等(equality)和等价(equivalence)的区别

#include #include #includeusing namespace std;bool ciStringCompare(const string l, const string r) {...
  • u014110320
  • u014110320
  • 2016年09月20日 23:36
  • 236

《Effective C++》设计与声明:条款18-条款19

这两个条款讲的是:接口的设计和类的设计。其中接口的设计原则是让接口容易被正确使用,不容易被误用;后面有一系列的做法。类的设计,讲的是类设计犹如新类型type的设计。在设计类时要考虑的一系列问题。...
  • KangRoger
  • KangRoger
  • 2015年01月21日 21:43
  • 1319

《Effective C++》学习笔记——条款31

《Effective C++》学习笔记——条款31:将文件间的编译依存关系降至最低
  • lx417147512
  • lx417147512
  • 2015年06月15日 13:51
  • 1364

《Effective C++》学习笔记——条款25

《Effective C++》学习笔记——条款25:考虑写出一个不抛异常的 swap 函数
  • lx417147512
  • lx417147512
  • 2014年12月30日 22:32
  • 835

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

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

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

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

《Effective C++》资源管理:条款13-条款15

在系统中,资源是有限的,一旦用完必须归还给系统,否则可能会造成资源耗尽或其他问题。例如,动态分配的内存如果用完不释放会造成内存泄漏。 这里说的资源不仅仅是指内存,还包括其他,例如文件描述符、网络连接、...
  • KangRoger
  • KangRoger
  • 2015年01月14日 21:46
  • 1282

《Effective C++》让自己习惯C++:条款1-条款4

《Effective C++》条款1到条款4。基本是总结C++的一些特点,尤其是不同于C语言的特点。...
  • KangRoger
  • KangRoger
  • 2014年12月13日 19:26
  • 2344

Effective Modern C++ 条款28 理解引用折叠

Effective Modern C++ 条款28
  • big_yellow_duck
  • big_yellow_duck
  • 2016年09月04日 19:34
  • 1984

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

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

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