浅谈智能指针shared

    xiaoxiao2021-03-26  4

              首先什么是智能指针?

          RAII:资源分配即初始化,通俗点来讲,就是定义一个类来封装资源的分配和释放,再构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。

          实现机制:是利用类的构造和析构函数(释放资源)是由编译器自动调用的。

          智能指针不仅管理执行对象的释放问题,还可以像指针一样的使用。

          C++标准库中主要有四个智能指针,分别是auto_ptr,shared_ptr,scoped_ptr和weak_ptr。auto_ptr是C++98标准化才引入的,scoped_ptr,shared_ptr和weak_ptr是C++11标准化才引入的。auto_ptr是对资源进行了转移,虽说简单,但使用起来都是坑,因此在任何情况下建议都不要使用;scoped_ptr是资源独占,防拷贝和赋值,将其拷贝函数和赋值操作的重载只给出私有的声明(面试中若写智能指针,可写scoped_ptr简单不易出错);shared_ptr是对资源的共享,可以拷贝和赋值(与weak_ptr结合使用),weak_ptr主要与shared_ptr配合使用,自己不能直接管理动态开辟的空间。

          下面我主要想谈一下shared_ptr。

          首先我们来看一下shared_ptr的代码实现:

    template<class T> class del { public: void operator()(T* _ptr) { delete _ptr; _ptr = NULL; } }; class Fclose { public: FILE* fclose(FILE* ptr) { cout << "fclose" << endl; fclose(ptr); } }; template<class T> class Free { public: T* free(T* ptr) { free(ptr); ptr = NULL; } }; template<class T,class Des=del> class shared_ptr { public: shared_ptr(T* ptr) :ptr(_ptr) , pcount(_pcount) { if (ptr != NULL) { *ptr = new int; *pcount = 1; } } shared_ptr(shared_ptr& sp) { _ptr = sp._ptr; _pcount = sp._pcount; if (_pcount) { (*_pcount)++; } } ~shared_ptr() { cout << "~shared_ptr()" << endl; if (_ptr&&!(--*_pcount)) { destroy(); } } shared_ptr& operator=(shared_ptr& sp) { if (!this == &sp) { if (_ptr && (--*pcount)) { destroy(); } _ptr = sp._ptr; _pcount = sp._pcount; (*_pcount)++; } return *this; } T* operator->() { return _ptr; } T& operator*() { return *_ptr; } private: void destroy() { del<T>()(_ptr); delete _pcount; _pcount = NULL; } T* _ptr; int* _pcount; };           虽然看起来好像没有什么问题了,但shared_ptr还存在三个问题:线程安全问题,需要定制删除器以及循环引用问题。

    线程安全问题:

           1.一个shared_ptr实体可被各个线程同时读取;

           2.两个的shared_ptr实体可以被两个线程同时写入,“析构”算写操作;

           3.如果要从各个线程读写同一个shared_ptr对象,那么需要加锁。

    定制删除器:

           在shared_ptr中被管理的资源都需要用delete来释放,当被管理的资源若是文件类型,或是用malloc开辟出来的动态空间,则无法使用delete来释放,若不做任何的处理,程序将奔溃,所以,我们要为其定制删除器。上面给出的代码就为其定制了删除器,使程序书写变得麻烦。

    循环引用问题:

           我们先来看一段代码:

    #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; #include"boost/shared_ptr.hpp" struct Node { Node(int value) :_value(value) { cout << "Node()" << endl; } ~Node() { cout << "~Node()" << endl; } shared_ptr<Node> _prev; shared_ptr<Node> _next; int _value; }; void Test2() { shared_ptr<Node> sp1(new Node(1)); shared_ptr<Node> sp2(new Node(2)); cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; sp1->_next = sp2; sp2->_prev = sp1; cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; } int main() { Test2(); return 0; }    

            构建shared_ptr的两个对象sp1,sp2,先构造的后释放,后构造的先释放,先释放的是sp2,那么它的引用计数为2,减去1之后成为1,不能进行释放,因为sp1还在管理这段空间,但是sp2这个变量已经被销毁,因为它是栈上的变量,但sp2管理的堆上的空间并没有释放。接下来,释放sp1,同样,先将引用计数减去1,引用计数变成了1,因此也不会释放sp1管理的动态空间。只有当sp1,sp2引用计数变成0时,空间才允许被释放。也就是说sp2要释放,必须要sp1释放,但sp1要释放,必须要等sp2释放。最终它们两个对象都有释放空间,造成了内存的泄露。

            下面根据下图,我们来理解一下sp1,sp2的构造过程,以及它们是如何利用shared_ptr智能指针来管理空间的。

          

           这是使用shared_ptr所构建出来的对象。一部分用来管理引用计数,另一部分用来管理节点。

          

           如图,便是上述双向链表最终构建出来的节点指针的指向。

           首先是构造智能指针sp1,先对其引用计数开辟空间,并初始化user和weaks均为1,然后将节点的空间交给__ptr来管理,引用计数的空间交给_Ref来管理。然后在构建sp2,构造过程与sp1相同。代码中sp2->prev=sp1与sp1->next=sp2调用过程相同,都是将各自的引用计数空间中的user加至2,然后sp1中的_next节点中的_Ref管理sp2的引用计数;sp2中的_prev节点中的_Ref管理的sp1的引用计数,_ptr管理sp1节点。析构时,先析构sp2,但引用计数中的user为1,无法释放空间,再去析构sp1,与sp2相同,也无法对空间进行释放。所以,开辟出来的四块动态内存空间,都没有释放。

           这就是shared_ptr的循环引用问题。

           以上就是对shared_ptr进行的讨论,若还有哪些地方没有涉及到欢迎补充。

       

    转载请注明原文地址: https://ju.6miu.com/read-600258.html

    最新回复(0)