【C++】智能指针(下)

简介: 【C++】智能指针(下)

std::shared_ptr


shared_ptr 是更靠谱的并且支持拷贝的智能指针。shared_ptr 的原理:是通过引用计数的方式来实现多个shared_ptr 对象之间共享资源。


    shared_ptr 在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。

    在对象被销毁时(也就是调用析构函数),就说明自己不使用该资源了,对象的引用计数减 一。

    如果引用计数是 0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

    如果引用计数不是 0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。


    share_ptr 的演示使用


    a60b2fd0f8b14bd2aef5e0d5e6a28254.png


    注:以下的实现方式是错误的。


    namespace Joy
    {
      template<class T>
      class shared_ptr
      {
      public:
        shared_ptr(T* ptr = nullptr)
          : _ptr(ptr)
        {
          ++_count;
        }
        ~shared_ptr()
        {
          if (--_count == 0 && _ptr)
          {
            cout << "Delete:" << _ptr << endl;
            delete _ptr;
          }
        }
        shared_ptr(shared_ptr<T>& sp)
          : _ptr(sp._ptr)
        {
          ++_count;
        }
        T& operator*()
        {
          return *_ptr;
        }
        T* operator->()
        {
          return _ptr;
        }
        int* GetCount() const
        {
          return &_count;
        }
      private:
        T* _ptr;
        static int _count; // 引用计数
      };
      template<class T>
      int shared_ptr<T>::_count = 0;
      void SharedPtrTest1()
      {
        Joy::shared_ptr<A> sp1(new A);
        Joy::shared_ptr<A> sp2(sp1);
        Joy::shared_ptr<A> sp3(sp2);
        Joy::shared_ptr<int> sp4(new int);
        Joy::shared_ptr<A> sp5(new A);
        Joy::shared_ptr<A> sp6(sp5);
      }
    }
    

    e61a771876b64b4d94fb59c842ad914a.png

    f43ca95839714e93b72d147b153a8839.png

    shared_ptr 的模拟实现


    namespace Joy
    {
      template<class T>
      class shared_ptr
      {
      public:
        shared_ptr(T* ptr = nullptr)
          : _ptr(ptr)
          , _pCount(new int(1))
        {}
        shared_ptr(const shared_ptr<T>& sp)
          : _ptr(sp._ptr)
          , _pCount(sp._pCount)
        {
          ++(*_pCount);
        }
        void Release()
        {
          // 减减对象的计数,如果是最后一个对象,要释放资源
          if (--(*_pCount) == 0)
          {
            cout << "Delete:" << _ptr << endl;
            delete _ptr;
            delete _pCount;
          }
        }
        ~shared_ptr()
        {
          Release();
        }
        shared_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
          // 通过_ptr来判断管理的资源是否是同一个。如果是同一个,
          // _ptr相等,直接返回即可
          //if(this == &sp)
          if (_ptr == sp._ptr)
          {
            return *this;
          }
          Release();  // 减减被赋值对象的计数,如果是最后一个对象,要释放资源
          // 共管新资源,++计数
          _ptr = sp._ptr;
          _pCount = sp._pCount;
          ++(*_pCount);
          return *this;
        }
        T& operator*()
        {
          return *_ptr;
        }
        T* operator->()
        {
          return _ptr;
        }
        int* GetCount() const
        {
          return _pCount;
        }
      private:
        T* _ptr;
        int* _pCount; // 引用计数
      };
      void SharedPtrTest2()
      {
        Joy::shared_ptr<A> sp1(new A);
        Joy::shared_ptr<A> sp2(sp1);
        Joy::shared_ptr<A> sp3(sp2);
        cout << sp1.GetCount() << endl;
        cout << sp2.GetCount() << endl;
        cout << sp3.GetCount() << endl;
        cout << "--------" << endl;
        Joy::shared_ptr<int> sp4(new int);
        cout << sp4.GetCount() << endl;
        cout << "--------" << endl;
        Joy::shared_ptr<A> sp5(new A);
        Joy::shared_ptr<A> sp6(sp5);    
        cout << sp5.GetCount() << endl;
        cout << sp6.GetCount() << endl;
        cout << "--------" << endl;
      }
    }
    

    51f2687f7aad4998a4b081debcd9262a.png


    注:shared_ptr 的赋值运算符重载需要防止自己给自己赋值的情况。只要两个对象管理的资源是一样的,那么这两个对象就不要相互赋值,直接 return 即可。我们可以通过 _ptr 或者 _pCount 是否相等来判断是否管理同一个资源。


    std::shared_ptr的循环引用


    struct Node
    {
      int _val;
      // 注:Node*和std::shared_ptr<Node>不是同一个类型
      std::shared_ptr<Node> _prev;
      std::shared_ptr<Node> _next;
      ~Node()
      {
        cout << "~Node()" << endl;
      }
    };
    // shared_ptr的循环引用
    void SharedPtrTest3()
    {
      std::shared_ptr<Node> node1(new Node);
      std::shared_ptr<Node> node2(new Node);
      node1->_next = node2;
      node2->_prev = node1;
    }
    

    28dd9ffd05434154b95711541ac1dc6f.png


    循环引用分析


    node1 和 node2 两个智能指针对象指向两个节点,引用计数变成 1,我们不需要手动 delete。

    node1 的 _next 指向 node2,node2 的_prev 指向node1,引用计数变成 2。

    node1 和 node2 析构,引用计数减到 1,但是 _next 还指向下一个节点,_prev 还指向上一个节点。

    也就是说 _next 析构了,node2 才能释放。

    也就是说 _prev 析构了,node1 才能释放。

    但是 _next 属于 node1 的成员,node1 释放了,_next 才会析构。而 node1 由 _prev 管理,_prev 属于node2 成员,node2 释放了,_prev 才会行。所以这就叫循环引用,谁也不会释放。

    aee7a4ea17eb496ab5f18af48067ed6c.png


    55ab880bcacf4877a18108a2f844b7c0.png


    shared_ptr 的循环引用问题通过 weak_ptr 来解决。weak_ptr 并不是常规的智能指针,没有 RAII,不支持直接管理资源。weak_ptr 主要用 shared_ptr 来构造,用来解决 shared_ptr 的循环引用问题。

    528344ee77c5414d9b31ce245a4fd144.png


    weak_ptr 调用拷贝构造和赋值运算符重载是不会增加资源的引用计数的,它不参与资源的管理。


    struct Node
    {
      int _val;
      std::weak_ptr<Node> _prev;
      std::weak_ptr<Node> _next;
      ~Node()
      {
        cout << "~Node()" << endl;
      }
    };
    // shared_ptr的循环引用
    void SharedPtrTest4()
    {
      std::shared_ptr<Node> node1(new Node);
      std::shared_ptr<Node> node2(new Node);
      // 注:use_count函数返回的是引用计数的大小
      cout << node1.use_count() << endl;
      cout << node2.use_count() << endl;
      cout << "--------" << endl;
      node1->_next = node2;
      node2->_prev = node1;
      cout << node1.use_count() << endl;
      cout << node2.use_count() << endl;
      cout << "--------" << endl;
    }
    

    46912ecb2ff34cde9e579a06de96784c.png


    weak_ptr 的简单模拟实现


    namespace Joy
    {
      // weak_ptr是辅助型智能指针,主要用于解决shared_ptr的循环引用问题
      template <class T>
      class weak_ptr
      {
      public:
        weak_ptr()
          : _ptr(nullptr)
        {}
        weak_ptr(const shared_ptr<T>& sp)
          : _ptr(sp.get())
        {}
        weak_ptr(const weak_ptr<T>& wp)
          : _ptr(wp._ptr)
        {}
        weak_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
          _ptr = sp.get();
          return *this;
        }
        weak_ptr<T>& operator=(const weak_ptr<T>& wp)
        {
          _ptr = wp._ptr;
          return *this;
        }
        T& operator*()
        {
          return *_ptr;
        }
        T* operator->()
        {
          return _ptr;
        }
      private:
        T* _ptr;
      };
      struct Node
      {
        int _val;
        // 注:Node*和std::shared_ptr<Node>不是同一个类型
        Joy::weak_ptr<Node> _prev;
        Joy::weak_ptr<Node> _next;
        ~Node()
        {
          cout << "~Node()" << endl;
        }
      };
      // shared_ptr的循环引用
      void SharedPtrTest5()
      {
        Joy::shared_ptr<Node> node1(new Node);
        Joy::shared_ptr<Node> node2(new Node);
        cout << node1.use_count() << endl;
        cout << node2.use_count() << endl;
        cout << "--------" << endl;
        node1->_next = node2;
        node2->_prev = node1;
        cout << node1.use_count() << endl;
        cout << node2.use_count() << endl;
        cout << "--------" << endl;
      }
    }
    

    e5472f7328284e1f8da685bcfa6939aa.png

    550d96a1d74c451484e968f66bd354ba.png


    shared_ptr 是有线程安全问题的,需要加锁保护。这部分内容等学习完线程部分再来补充。


    定制删除器


    如果不是 new 出来的对象如何通过智能指针管理呢?或者是 new[ ] 出来的对象如果通过智能指针管理呢?那么就需要给 shared_ptr 设计定制删除器来解决这个问题。


    b14f41ce5cd641ab9866ee491d76b297.png


    new 内置类型,内置类型不会去调用构造函数。new[ ] 调用的是 operator new[ ],operator new[ ] 调用的是 malloc;delete[ ] 调用的是 oprator delete[ ],operator delete[ ] 调用的是 free。因为 delete[ ] 和 delete 都是调用 free 来释放空间,所以对于内置类型不会存在太大的问题(可能一些编译器会进行是否匹配使用的检查)。而对于自定义类型,就需要看该类型有没写析构函数。如果写了析构函数,使用 new[ ] 时,VS 会在最前面多开四个字节来存储对象的个数。当你使用 delete 来释放 new[ ] 申请的空间时,就会存在释放的位置不对且析构函数少调用了的问题。因为 VS 还给你多开了四个字节的空间,来存储对象的个数。而如果你没有写析构函数,那么编译器会自动生成析构函数。编译器自己生成析构函数的话,就不会在前面多开一个字节来存储申请空间的个数,也不会去调用析构函数,所以程序就没有报错(与内置类型不报错的原因类似)。

    1c1955a038af4324bf852736cf6d4d2f.png

    f59804ac280a44e18920d7a419e2c33c.png

    template<class T>
    struct DeleteArray
    {
      void operator()(T* ptr)
      {
        cout << "delete[]:" << ptr << endl;
        delete[] ptr;
      }
    };
    template<class T>
    struct Free
    {
      void operator()(T* ptr)
      {
        cout << "free:" << ptr << endl;
        free(ptr);
      }
    };
    // 定制删除器
    void SharedPtrTest7()
    {
      std::shared_ptr<Node> n1(new Node[5], DeleteArray<Node>());
      std::shared_ptr<Node> n2(new Node);
      std::shared_ptr<int> n3(new int[5], DeleteArray<int>());
      std::shared_ptr<int> n4((int*)malloc(sizeof(int)), Free<int>());
    }
    

    54479e7baea64fb3bcc158c7e63b1080.png


    注:在拷贝构造时传给 shared_ptr 的定制删除器也可以是 lambda 表达式,传给 unique_ptr 的定制删除器是定制删除器的类型。


    f135385ab7ef4fcb8923aa5fe6b1e144.png


    我们的改造 unique_ptr 和 shared_ptr 都是在类模板里传定制删除器的类型。


    namespace Joy
    {
      template<class T>
      struct Delete
      {
        void operator()(T* ptr)
        {
          //cout << "delete:" << ptr << endl;
          delete ptr;
        }
      };
      template<class T>
      struct DeleteArray
      {
        void operator()(T* ptr)
        {
          cout << "delete[]:" << ptr << endl;
          delete[] ptr;
        }
      };
      template<class T>
      struct Free
      {
        void operator()(T* ptr)
        {
          cout << "free:" << ptr << endl;
          free(ptr);
        }
      };
      template<class T, class D = Delete<T>>
      class unique_ptr
      {
      public:
        unique_ptr(T* ptr = nullptr)
          : _ptr(ptr)
        {}
        // C++98的防拷贝做法:将拷贝构造和赋值运算符重载弄成私有,只声明不实现
        // C++11的防拷贝做法:delete
        unique_ptr(unique_ptr<T>& up) = delete;
        unique_ptr<T>& operator=(unique_ptr<T>& up) = delete;
        ~unique_ptr()
        {
          if (_ptr) 
          {
            cout << "Delete:" << _ptr << endl;
            D()(_ptr);
          }
        }
        T& operator*()
        {
          return *_ptr;
        }
        T* operator->()
        {
          return _ptr;
        }
      private:
        T* _ptr;
      };
      template<class T, class D = Delete<T>>
      class shared_ptr
      {
      public:
        shared_ptr(T* ptr = nullptr)
          : _ptr(ptr)
          , _pCount(new int(1))
        {}
        shared_ptr(const shared_ptr<T>& sp)
          : _ptr(sp._ptr)
          , _pCount(sp._pCount)
        {
          ++(*_pCount);
        }
        void Release()
        {
          // 减减对象的计数,如果是最后一个对象,要释放资源
          if (--(*_pCount) == 0)
          {
            //cout << "Delete:" << _ptr << endl;
            D()(_ptr);
            delete _pCount;
          }
        }
        ~shared_ptr()
        {
          Release();
        }
        shared_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
          // 通过_ptr来判断管理的资源是否是同一个。如果是同一个,
          // _ptr相等,直接返回即可
          //if(this == &sp)
          if (_ptr == sp._ptr)
          {
            return *this;
          }
          Release();  // 减减被赋值对象的计数,如果是最后一个对象,要释放资源
          // 共管新资源,++计数
          _ptr = sp._ptr;
          _pCount = sp._pCount;
          ++(*_pCount);
          return *this;
        }
        T& operator*()
        {
          return *_ptr;
        }
        T* operator->()
        {
          return _ptr;
        }
        int* GetCount() const
        {
          return _pCount;
        }
        T* get() const
        {
          return _ptr;
        }
        int use_count()
        {
          return *_pCount;
        }
      private:
        T* _ptr;
        int* _pCount; // 引用计数
      };
      void SharedPtrTest8()
      {
        Joy::shared_ptr<Node, DeleteArray<Node>> n1(new Node[5]);
        Joy::shared_ptr<Node> n2(new Node);
        Joy::shared_ptr<int, DeleteArray<int>> n3(new int[5]);
        Joy::shared_ptr<int, Free<int>> n4((int*)malloc(sizeof(int)));
      }
    }
    

    c51ccd04742e415e941f5a1bfaee77d5.png


    智能指针重点内容


    bc683ea60e9b4ec58eb868c3fe502643.png

    👉C++11和boost中智能指针的关系👈


    C++ 98 中产生了第一个智能指针 auto_ptr。

    C++ boost 给出了更实用的 scoped_ptr、shared_ptr 和 weak_ptr。

    C++ TR1 引入了 shared_ptr 等。不过注意的是 TR1 并不是标准版。

    C++ 11 引入了 unique_ptr、shared_ptr 和 weak_ptr。需要注意的是 unique_ptr 对应 boost 的scoped_ptr,并且这些智能指针的实现原理是参考 boost 中的实现的。


    👉总结👈


    本篇博客主要讲解了什么是内存泄漏、内存泄漏的危害及分类、什么是智能指针和 RAII、auto_ptr、unique_ptr、shared_ptr 和 weak_ptr 以及定制删除器等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

    相关文章
    |
    6天前
    |
    C++ 数据格式
    LabVIEW传递接收C/C++DLL指针
    LabVIEW传递接收C/C++DLL指针
    16 1
    |
    1天前
    |
    C++
    C++程序返回指针值的函数
    C++程序返回指针值的函数
    10 1
    |
    1天前
    |
    存储 C++
    C++程序数组与指针:深入理解与实践
    C++程序数组与指针:深入理解与实践
    9 1
    |
    1天前
    |
    存储 C++
    C++程序指针变量:深入理解与实践
    C++程序指针变量:深入理解与实践
    7 1
    |
    1天前
    |
    存储 C++
    C++程序中的对象指针
    C++程序中的对象指针
    7 1
    |
    1天前
    |
    存储 C++
    C++程序中的函数与指针
    C++程序中的函数与指针
    6 1
    |
    1天前
    |
    存储 C++
    C++程序中的字符串与指针
    C++程序中的字符串与指针
    8 2
    |
    6天前
    |
    编译器 C++
    C/C++杂谈——指针常量、常量指针
    C/C++杂谈——指针常量、常量指针
    9 0
    |
    6天前
    |
    C++ 编译器
    |
    6天前
    |
    存储 安全 程序员
    C++:智能指针
    C++:智能指针
    22 5