Effective C++(三)内存与资源管理

内存是必须管理的众多资源之一,其他常见的资源还包括文件描述起、互斥锁、图形界面中的字型和笔刷、数据库连接、以及网络sockets。

  1. 条款13:以对象管理资源

    • 请记住:
      • 为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源
      • 两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它指向null。
    • 把资源放进对象内,我们便可倚赖C++的“析构函数自动调用机制”确保资源被释放
      Investment * createInvestment();
      void f()
      {
      Investment * pInv = createInvestment();
      delete pInv;
      }
      
      void f()
      {
      std::auto_ptr<Investment> pInv(createInvestment());
      }
      
    • “以对象管理资源”有两个关键的想法:
      • 获得资源后立刻放进管理对象。“以对象管理资源”的观念常被称为“资源取得时机便是初始化时机(Resource Acquisition Is Initialization;RAII)”。每一笔资源都在获得的同时立刻被放进管理对象中。
      • 管理对象运用析构函数确保资源被释放。无论控制流如何离开区块,一旦对象被销毁(例如当对象离开作用域)其析构函数自然会被自动调用,于是资源被释放。如果资源释放动作可能导致抛出异常,事情变得有点棘手。
    • 别让多个auto_ptr同时指向同一对象。如果对象会被删除一次以上,则会产生未定义行为。auto_ptr有一个不寻常的性质:若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一拥有权!“受auto_ptrs管理的资源必须绝对没有一个以上的auto_ptr同时指向它”,意味auto_ptr并非管理动态分配资源的神兵利器。
    • auto_ptr的替代方案是“引用计数型智慧指针”(reference-counting smart pointer;RCSP)。所谓的RCSP也是个智能指针,持续跟踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。
    • tr1::shared_ptr。tr1::shared_ptr的复制行为“一如预期”
      void f()
      {
      std::tr1::shared_ptr<Investment> pInv1(createInvestment());
      std::tr1::shared_ptr<Investment> pInv2(pInv1);
      pInv1 = pInv2;
      }
      
    • auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]动作。
  2. 条款14:在资源管理类中小心copying行为

    • 请记住:
      • 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为
      • 普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法。不过其他行为也都可能被实现
    • Mutex的互斥器对象。假设lock和unlock函数:
      void lock(Mutex *pm);
      void unlock(Mutex *pm);
      //class Lock
      class Lock{
      public:
      explicit Lock(Mutex *pm):mutexPtr(pm)
      {
        lock(mutexPtr);
      }
      ~Lock(){ unlock(mutexPtr); }
      private:
      Mutex *mutexPtr;
      }
      //客户对Lock使用
      Mutex m;
      Lock m1(&m);
      
    • 当一个RAII对象被复制,会发生什么事?
      • 禁止复制。根据条款6告诉你:将copying操作声明为private。
      • 对底层资源祭出“引用计数法”。tr1::shared_ptr的缺省行为是“当引用次数为0时删除其所指物”。当我们用上一个Mutex,我们想要做的释放动作是解除锁定而非删除。幸运的是tr1::shared_ptr允许指定所谓的“删除器(deleter)”,那是一个函数或函数对象,当引用次数为0时便被调用(此机能并不存在于auto_ptr+它总是将其指针删除)。删除器将tr1::shared_ptr构造函数而言是可有可无的第二参数。
        class Lock{
        public:
        explicit Lock(Mutex *pm) : mutexPtr(pm, unlock)
        {
        lock(mutexPtr.get());
        }
        private:
        std::tr1::shared_ptr<Mutex> mutexPtr;
        };
        
      • 复制底部资源。
      • 转移底部资源的拥有权。这是auto_ptr的复制意义。
  3. 条款15:在资源管理类中提供对原始资源的访问

    • 请记住:
      • APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理之资源”的办法
      • 对原始资源的访问可能经由显式转换或隐式转换。一般而言,显式转换比较安全,但隐式转换对客户比较方便。
    • 取得原始资源。有显式转换和隐式转换。tr1::shared_ptr和auto_ptr都提供一个get成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件)。int days = daysHeld(pInv.get());就像所有智能指针一样,tr1::shared_ptr和auto_ptr也重载了指针取值操作符,它们允许隐式转换至底部原始指针
  4. 条款16:成对使用new和delete时要采取相同形式

    • 请记住:如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果在new表达式中不使用[],一定不要在相应的delete表达式中使用[]
    • 当你使用new动态生成一个对象。有两件事发生。第一,内存被分配出来。第二,针对此内存会有一个构造函数调用。当你使用delete,有两件事发生:针对此内存会有一个析构函数被调用,然后内存才被释放。
    • 因为单一对象的内存布局一般而言不同于数组的内存布局。数组的所用内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。
    • 最好尽量不要对数组形式做typedefs动作。因为C++标准程序库含有string, vector等templates,可将数组的需求降至几乎为零。
  5. 条款17:以独立语句将newed对象置入智能指针

    • 请记住:以独立语句将newed对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。
    • processWidget( std::tr1::shared_ptr<Widget>(new Widget), priority() ); 避免这类问题的办法很简单:使用分离语句,分别写出(1)创建Widget(2)将它置入一个智能指针内,然后再把那个智能指针传给processWidget。
      std::tr1::shared_ptr<Widget> pw( new Widget );
      processWidget( pw, priority() );
      
      因为编译器对于“跨越语句的各项操作”没有重新排列的自由(只有在语句内它才拥有那个自由度)。

猜你喜欢

转载自blog.csdn.net/u010991048/article/details/38277557