• 三、资源管理--条款13-15


    概述

    • 资源就是一旦用了它,以后必须还给系统的东西。C++中最常用的资源就是动态内存分配。其它的资源还包括文件描述符器、互斥锁、图形界面中的字型和笔刷、数据库连接、以及网络socket
    • 无论是哪一种资源,我们都要确保当自己使用完之后还给系统。

    条款13:以对象管理资源

    1. 资源并没有还给系统。

    概述中已经说到,资源用完之后要还给系统。 我们考虑以下函数会发生什么:

    void f()
    {
        Investment * pInv = createInvestment();
        ...
        delete pInv;
    }
    

    1.1 倘若我们在delete之前的函数中有一个分支,会进行return。那么我们就永远不会执行delete,这样函数执行结束之后并没有将动态分配的资源还给系统,会有内存泄漏的风险。

    1.2 倘若中间有goto语句,也是如此。

    1.3 倘若抛出了异常,delete函数也无法被执行到。

    2.1 使用智能指针————auto_ptr.

    void f()
    {
        std::auto_ptr<Investment> pInv(createInvesment());
        ...
    }
    

    当智能指针出了函数的作用域,会调用其析构函数自动删除pInv.

    这个简单例子示范“以对象管理资源”的两个关键想法:

    • 获得资源后立即放入管理对象。“资源取得时机便是初始化时机”。(个人认为如果不第一时间放进管理对象,那么很有可能代码因为某种原因return了,内存就泄漏了。)
    • 管理对象运用析构函数确保资源被释放。

    auto_ptr的一个性质:

    auto_ptr<Investment> pInv1(createInvestment()); //pInv1指向对象
    auto_ptr<Investment> pInv2(pInv1); //pInv指向对象,pInv1为null
    pInv1 = pInv2;  //pInv1指向对象,pInv2为null
    

    这块代码已经注释,也就是aotu_ptr的性质:

    如果通过copy构造函数或copy assignment函数复制它们,原来的auto_ptr将变成null,新的指向才指向对象。

    原因:

    如果让多个auto_ptr指向了同一个对象,那么如果多个auto_ptr的析构函数调用,会对一个对象进行多次删除,但实际上第一次删除之后就不存在了,后面的删除会造成未定义的行为。

    2.2 使用“引用计数型智慧指针”替代auto_ptr

    也就是shared_ptr:

    void f()
    {
        shared_ptr<Investment> pInv1(createInvestment()); //pInv1指向对象
        shared_ptr<Investment> pInv2(pInv1); //pInv2指向对象,pInv1也指向对象
        pInv1 = pInv2;  //pInv1指向对象,pInv2也指向对象
    }
    

    在这个函数里面,shared_ptr允许多个指针指向同一个对象。它会对指针进行计数,直到计数为0的时候才会调用析构函数,删除对象。所以并不会出现多次删除同一个对象的情况。

    注意:

    auto_ptr和shared_ptr在其析构函数中做的都是delete操作而没有delete[]操作。所以我们要注意别在动态分配的array中使用这两个指针。

    auto_ptr<string> aps(new string[10]);
    shared_ptr<int> spi(new int[1024]);
    

    这两个操作都是十分危险的。

    作者总结:

    为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。

    两个常被使用的RAII classes分别是shared_ptr和auto_ptr.前者是较佳的选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它指向null.

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

    当我们使用shared_ptr的时候,当引用计数变为0时,会删除这个对象。但实际情况里,我们可能不想要删除。

    比如一个互斥锁的类,构造的时候就加锁,析构的时候解锁,这样也符合RAII对象的特性。

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

    现在让我们看看,在调用的时候进行copy的行为会造成什么:

    Mutex m;
    ...
    {
        Lock m1(&m);
        ...
    }
    

    这段代码是正确的行为,在出作用域的时候,析构函数被调用,就会自动解锁。

    但是如果客户端出现copy行为:

    Lock m1(&m);
    Lock m2(m1);
    

    这会造成m2还在使用资源的时候,假如m1析构函数调用了,那么资源就被释放了,m2也就无法使用了。

    解决方法:

    • 禁止复制。用条款6中所述的使用一个Uncopyable类,通过将拷贝构造声明为private的来禁止调用。

    • 对底层资源祭出“引用计数法”。 这个方法要注意shared_ptr在计数为0的时候会删除这个对象,但是我们只需要释放它,所幸shared_ptr允许我们指定一个删除器。我们可以改成:

      class Lock
      {
      public:
      explicit Lock(Mutex *pm)
      :mutexPtr(pm,unlock)
      {
      lock(mutexPtr.get()); // 获得资源,get函数是获取原始指针
      }
      private:
      shared_ptr mutexPtr;
      }
      我们指定看一个unlock函数作为删除器,计数器为0的时候会执行。所以我们无需一个析构函数。

    • 复制底部资源。就是执行“深拷贝”。“深拷贝”是会在新的内存中存一份和原数据一模一样的数据,不会使用原来的地址。那么就是两份相同数据存在不同的地方。在这个例子里,就是两份资源了,释放掉原来的资源并不影响新的资源。

    • 转移底部资源所有权。 确保只有一个RAII对象指向一个资源,复制时候资源的所有权从被复制物转向目标物。 可以参考auto_ptr的做法,讲原来的指针置为null,确保只有一份资源被使用。

    作者总结

    复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定对象的copying行为。

    普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。

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

    1. shared_ptr获得原始指针,只需要调用get函数就可以。

      shared_ptr pInt;
      int *p = pInt.get();

    2. shared_ptr和auto_ptr都重载了指针取值的方法。

      class Investment
      {
      public:
      bool isTaxFree() const;
      ...
      };

    此时执行:

    shared_ptr<Investment> pInv(createInvestment());
    bool taxFree1 = pInv->isTaxFree();
    bool taxFree2 = (*pInv).isTaxFree();
    

    以上"->"运算符和"."运算符都是适用的。

  • 相关阅读:
    在线pdm查看
    vscode
    idea for Mac 代码提示设置
    定位功能
    canvas刮奖
    jquery生成二维码
    Redux DevTools浏览器插件调试redux
    .gitignore
    HBuilder在MAC下的SVN
    UMD编码规范
  • 原文地址:https://www.cnblogs.com/love-jelly-pig/p/9629800.html
Copyright © 2020-2023  润新知