Effect C++ 笔记 【3 Resource Management】

来源:互联网 发布:中科大ipv6网络电视 编辑:程序博客网 时间:2024/05/17 04:42

条款13: 以对象管理资源

      动态分配的资源,很容易忘记delete,或者程序在某处return而没有执行delete。因此,用对象来管理资源(类对象,在区块结束后,自动调用析构函数释放所有资源)。 //后面介绍的就是用 智能指针 这种对象,替代原始的指针管理资源(就是指向那个资源)

      以对象管理资源的两个关键想法:

a. 获得资源后立即放进管理对象

b. 管理对象运用析构函数确保资源被释放

 

auto_ptr是个“类指针(pointer-like)对象”,就是所谓的“智能指针”。

为防止多个对象(即智能指针对象)指向同一个资源,导致这个资源被delete多次。auto_ptr有个不同寻常的性质: 通过拷贝构造或拷贝赋值复制它们,它们会变成null,而赋值所得指针将取代资源的唯一拥有权。

 

“引用计数型指针”(reference-counting smart pointer; RCSP)。 也是个智能指针。持续追踪共有多少对象指向某资源,并在无人指向它时自动删除资源。 但RCSP无法打破环状引用(互相指,好像都还在“被使用”)。TR1 的 tr1::shared_ptr就是个RCSP。

 

auto_ptr和tr1::shared_ptr两者都是在其析构函数内做 delete 而不是 delete[] 动作(条款16描述)。  

没有针对“C++动态分配数组”而设计的东西。因为vector和string几乎可以取代动态分配而得的数组。

 

Tips:   1. 为防止资源泄露,使用“RAII(资源取得之时便是初始化之时)对象”,他们在构造函数中获得资源并在析构函数中释放。//就是用智能指针 取代原始指针,指向资源对象,从而控制资源

            2. 两个常用RAII classes 分别是 tr1::shared_ptr 和 auto_ptr 。前者copy行为比较直观。后者,复制动作会使它指向null。

 

条款14: 在资源管理类中 小心 coping 行为

//理解不深  以后再看

 

条款15: 在资源管理类中提供对原始资源的访问

//  有时API需要原始指针,比如大量 C API 。那么用智能指针管理对象时,就必须提供一个转换方法,返回资源对象的原始指针。

 

Tips: 1. API要求访问原始资源,所以每一个RAII class 应该提供一个“取得其所管理之资源”的办法

        2. 对原始资源的访问,可能经由显示转换 或 隐式转换。 显式转换安全,隐式方便。

 

条款16: 成对使用 new 和 delete 时要采用相同形式

当你使用 new , 有两件事发生:

 
  1. 内存被分配出来(通过名为operator new的函数,条款59和51)
  2. 针对此内存,会有一个(或更多)构造函数被调用。

当你使用 delete ,也有两件事:

 
  1. 针对此内存,会有一个(或更多)析构函数被调用。
  2. 然后,内存才被释放(通过名为 operator delete的函数)

单一对象的内存布局,一般而言不同于数组的内存布局。

数组所用内存通常包括“数组大小”的记录,以便delete 知道需要调用多少次析构函数。 

比如,可以想象内存布局如下,n是数组大小:       (编译器不一定非这么实现,但很多的确是这样做的)

------------

|  Object  |

------------

-----------------------------------------------------

|  n ||  Object  ||  Object  ||  Object  ||  Object  |

-----------------------------------------------------

例如:  std::string* stringPtr1 = new std::string;

           std::string* stringPtr2 = new std::string[100];

           ……

           delete stringPtr1;              //删除一个对象

           delete [] stringPtr2;           // 删除一个由对象组成的数组

如果对stringPtr1 使用 "delete []" 形式,结果未有定义。因为,delete会读取若干内存并将它解释为“数组大小”,然后再调用析构函数。

 

【规则很简单】:  new时使用 [ ] , delete时也使用 [ ]  ;  new时没有使用 [ ] , delete时也不用 [ ]

 

尽量不对数组形式做 typedef 动作,因为容易让人分不清究竟是用何种delete。这容易做到,因为C++标准库含有 string,vector等templates,可将数组要求降至几乎为零。   // 理解不深,有空温习一下

 

条款17:以独立语句将 newed 对象置入只能指针

例:     

int priority()

void processWidget(std::tr1::shared_ptr<Widget> pw , int priority)

现在调用

                     processWidget(std::tr1::shared_ptr<Widget>(new Widget) , priority() )     //  可能泄露资源!!

原因:  编译器产出一个 processWidget 调用代码前,必须先核算即将传递的各个实参。

      上述第二个实参,是单纯 priority 函数的调用。

      但,第一个实参  std::tr1::shared_ptr<Widget>(new Widget) 分成两部分:

  •  
    • 执行 new Widget
    • 调用 tr1::shared_ptr 构造函数

于是,在调用 processWidget 之前,编译器必须创建代码,做三件事:

  • 调用 priority
  • 执行 new Widget
  • 调用 tr1::shared_ptr 构造函数

    但C++ 编译器执行这三件事的顺序弹性很大(其他语言不一定,如java总以特定顺序完成参数核算)。

    可以确定, new Widget 一定执行于 tr1::shared_ptr 构造函数前, 因为要被用到。

    但是,调用 priority 可以放在任何位置执行。 

    如果放在中间,比如

    1. 执行 new Widget
    2. 调用 priority
    3. 调用 tr1::shared_ptr 构造函数

    那么,当 2 中出现异常。 new Widget 返回的指针就会遗失,造成资源泄漏。

     

    避免此类问题方法:  分离语句。

    1. 创建 Widget
    2. 将它置入一个只能指针
    3. 把智能指针传给 processWidget

    std::tr1::shared_ptr<Widget> pw ( new Widget );   //  单独语句内只能指针存储 new 所得对象

    processWidget(pw , priority() );                            //  这个动作绝不会造成泄漏

     

    编译器对于”跨越语句的各项操作“没有重新排列的自由(只有语句内,才有那个自由度)          //  拆开的语句,对执行顺序限定的好!

    原创粉丝点击