阿龙的学习笔记---Effective C++---第三章、资源管理

 所谓资源就是,一旦用了它,将来必须还给系统。如果不这样,糟糕的事情就会发生。
 C++程序中最常使用的资源就是动态分配内存(如果你分配内存却从来不曾归还它,会导致内存泄露),
 但内存只是你必须管理的众多资源之一。其他常见的资源还包括文件描述符(file description),互斥锁(mutex lock),图形界面中的字型和笔刷,数据库连接,以及网络socket。
 不论哪一种资源,最重要的是,当你不再使用它时,必须将它还给系统。


  • 条款13:以对象管理资源

    • 假设我们使用一个投资行为的程序库,各种股票、债券等,使用一个基类 class Investment:

      // “投资类型”集成体系中的root class
      class Investment { ... };
      

      进一步假设,这个程序库通过一个工厂函数(factory function)供应特定的Investment对象,对象是动态分配的,返回一个指针,那么调用者则必须要管理这个资源,适当的时候要delete,否则会有内存泄漏等风险:

      // 返回指针,指向Investment继承体系内的动态分配对象
      // 调用者有责任删除它
      Investment* createInvestment();
      

      假如现在写一个函数 f() 使用程序库完成任务,需要在最后delate以完成资源管理。

      void f(){
          // 调用factory函数
          Investment* pInv = createInvestment();
          ...
          // 释放pInv所指对象
          delete pInv;
      }
      

      但是,程序运行过程并不如我们想象。首先,会有可能在delete之前的某个程序分支进行了return,这样则跳过了delete。或者,在delete之前的函数抛出了异常,delete也会被跳过。如果delete被跳过,则pInv指向的内存,以及里面保存的资源都会发生泄露。所以这是一种不好的方法。

    • 为了确保createInvestment返回的资源被正确的释放:把资源放进对象内,我们便可倚赖C++的“析构函数自动调用机制”确保资源被释放。

    • 标准库提供的std::auto_ptr正是针对这种情况而设计的模板。std::auto_ptr是个“类指针(pointer-like)对象”,也就是所谓“智能指针”,其析构函数自动对其所指对象调用delete。

      使用std::auto_ptr来重写 f() 函数:

      void f(){
          // 调用factory函数
          std::auto_ptr<Investment> pInv(createInvestment());
          // 一如既往的使用pInv
          ...
          // 经由auto_ptr的析构函数自动删除pInv
      }
      
    • 以对象管理资源的两个关键点在于:
      1、获得资源后立刻放到管理对象中;
      2、管理对象运用析构函数确保资源被释放。

    • auto_ptrshared_ptr

      auto_ptr的一个局限性在于,不能让多个指针指向一个对象,会发生多次释放而产生不可预期的错误。(所以auto_ptr的拷贝构造和operator=函数会将源对象变成null,保证只有一个智能指针指向资源)。STL容器也需要用于能够复制的对象。

      替代方案是采用引用计数型智能指针(reference-counting smart pointer)。他会支持追踪有多少对象指向了资源,在最后没人指向的时候将其释放。std::tr1::shared_ptr就是一种。

      void f(){    
          // 调用factory函数
          std::tr1::shared_ptr<Investment> pInv(createInvestment());
          std::tr1::shared_ptr<Investment> pInv2(pInv);
          // 使用pInv一如既往
          ...
          // 自动销毁pInv和pInv2,不会重复delete
      }
      
    • 总结
      为了防止资源泄露,可采用以对象管理资源的方式;
      两个经常被使用的智能指针class是:auto_ptr 和 tr1::shared_ptr。


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

    • 有些时候会用到自定义的资源管理类,在设计时要考虑到coping行为(即copy构造函数及operator=)。比如下面这个用于管理mutex互斥锁的管理类,为了防止用户获取锁之后忘记释放锁,在析构函数中将mutex锁释放。

      class Lock{
      public:
          Lock(Mutex* pm) : mutexPtr(pm)
          { lock(mutexPtr);}    //获得资源
          ~Lock(){ unlock(mutexPtr);}    //释放资源
      private:
          Mutex *mutexPtr;
      }
      

      使用时可以这样使用,只需要在代码块开头定义一个lock对象,将锁指针传入,在代码块末尾会调用析构函数完成解锁。

      Mutex m;        //定义你需要的互斥器
      ...
      {               //建立一个区块用来定义critical section
          Lock ml(&m);//锁定互斥器
          ...         //执行critical section内的操作
      }               //在区块最末尾,自动解除互斥器锁定
      

      但是如果发生复制coping行为,默认的coping函数,会发生两次解锁,产生不可预期的后果。

    • 解法之一是禁止复制,如条款06中,将coping两个函数声明为private,或者更好的方法设计一个基类,声明他的coping函数为private。

    • 解法之二是对底层资源采用“引用计数法”(reference-count),类似上一条中的shared_ptr的原理,可以直接利用share_ptr来实现。一般在class中包含一个shared_ptr则可以让class拥有reference-counting copying的行为。

      shared_ptr的功能是在资源数为0的时候才进行删除,但删除并不能作用于mutex,我们需要进行解锁操作,shared_ptr提供了一个功能,能够传入一个函数指针代替原本的删除操作,本例中则将删除delete变成解锁unlock

      class Lock{
      public:
           Lock(Mutex* pm)    	//以某个Mutex初始化shared_ptr
              : mutexPtr(pm, unlock)  // 并以unlock函数为删除器
          {
              lock(mutexPtr.get());		//get获取到mutexPtr指向的锁
          }
      private:
          std::tr1::shared_ptr<Mutex> mutexPtr;    //使用shared_ptr
      }
      

      Lock类中拥有了一个private share_ptr对象,在发送coping行为时,编译器自动生成的copy函数直接复制mutexPtr。

      而在自动生成的析构函数中,会自动调用mutexPtr的析构函数,而由于shared_ptr的特性,会在最后一个时解锁。

    • 在auto_ptr中还给出一种解决方法,就是将资源对象转移到目标对象,将源对象置为空。


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

    • 在一些情况下,许多API需要调用资源管理类管理的原始资源,我们需要提供相应的机制。
    • 其中有两种方式:显示转换隐式转换
    • 首先看一下显示转换:例如shared_ptr和auto_ptr,都提供了一个get()函数直接返回智能指针内部的原始指针(的复件)
    • 对于隐式转换,可利用operator隐式转换函数,参考这个文章:operator隐式转换。同时,例如shared_ptr和auto_ptr也提供了operator-> 和 operator*,使得可以像使用指针那样使用智能指针。
    • 一般来说,显示转换看起来麻烦,但是安全性高,而隐式转换虽然用法自然,但可能会发生意料之外的转换。

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

    • 对于new分配的内存,要使用delete来释放;对于new[]分配的内存,要用delete[]来释放。
    • 对于类中的多个构造函数,要以相同的方式new,否则析构函数中对应就会有错误。

  • 条款17:以独立语句将newed对象置于智能指针

    • 这是为了保证资源在创建后,立刻进入智能指针或其他资源管理类。
    • 假如在这两个操作之间有其他操作,可能会抛出异常等,那么资源就会被泄露,而不会被管理类销毁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值