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》条款3-条款4

条款3使容器里对象的拷贝操作轻量而正确 条款4用empty来代替检查size是否为0
  • KangRoger
  • KangRoger
  • 2015-10-08 23:31:54
  • 948

Effective C++ 条款9

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

Effective STL 中文版(完整版)

Winter总算找到《Effective STL》的完整中文版了,奉献给大家。书中作者解释了怎样结合STL组件来在库的设计得到最大的好处。这样的信息允许你对简单、直接的问题开发简单、直接的解决方案,也...
  • sdnxiaotao
  • sdnxiaotao
  • 2008-07-05 13:23:00
  • 501

Effective STL 条款9

在删除选项中仔细选择本节核心内容: 如何高效的删除容器中的指定数据结论如下: 1.去除一个容器中有特定值的所有对象如果容器是vector,string或者deque,使用erase-remove惯...
  • u011058765
  • u011058765
  • 2016-04-15 08:59:34
  • 176

Effective STL 条款3

确保容器中的对象拷贝正确而高效在使用STL模板的时候,我们必须想到有关拷贝的问题。如果STL中存储的是我们自定义类型。可能会发生以下几个问题:首先为了避免拷贝过程成为程序运转的瓶颈,我们选择存储自定义...
  • u011058765
  • u011058765
  • 2015-06-21 21:13:01
  • 486

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

条款44将与参数无关的代码抽离templates 条款45运用成员函数模板接受所有兼容类型...
  • KangRoger
  • KangRoger
  • 2015-03-12 22:01:36
  • 1566

Effective STL条款17-条款18

条款17:使用交换技巧来修正过剩容量本节条款告诉我们,如果你有一个vector的容器,容器的容量是10000,但是,现在只用了1,那么为了节省内存,我们应该只保留使用的vector容量,多余的容量应该...
  • u011058765
  • u011058765
  • 2016-04-21 09:10:41
  • 302

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

《Effective C++》学习笔记——条款24:若所有参数皆需类型转换,请为此采用non-member函数...
  • lx417147512
  • lx417147512
  • 2014-12-26 23:38:29
  • 939

《Effective C++》总结

从C转向C++部分: 1.  条款1:尽量用const和inline而不用#define 1)  使用#defineNUM 1.5时,对应代码是预编译时检查,并将所有的NUM替换为1.5,这样在编译...
  • sgs1018
  • sgs1018
  • 2013-11-27 16:06:32
  • 1025

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

《Effective C++》学习笔记——条款31:将文件间的编译依存关系降至最低
  • lx417147512
  • lx417147512
  • 2015-06-15 13:51:45
  • 1459
收藏助手
不良信息举报
您举报文章:Effective STL 条款7
举报原因:
原因补充:

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