zoukankan      html  css  js  c++  java
  • Effective STL 条款7 Anthony

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

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

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

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

    void doSomething()

    {

    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的时代将永远不会到来。

  • 相关阅读:
    Spring核心之IoC
    【jmeter】jMeter使用Badboy录制Web测试脚本
    【jmeter】jmeter环境搭建
    【jmeter】Jmeter启动GUI界面出错
    【appium】keyevent的keycode
    !!!!!!!【unittest】unittest需要懂的的技术
    【unittest】unittest单元模块做assert
    【appium】根据UIAutomator定位元素
    【appium】根据class_name定位元素
    【appium】根据name定位元素
  • 原文地址:https://www.cnblogs.com/ahuangliang/p/5309275.html
Copyright © 2011-2022 走看看