C++:智能指针及其实现原理

更多C++知识点:C++目录索引


1. RAII思想

  1. 定义一个类来封装资源的分配与释放,

  2. 构造函数中完成资源的分配及初始化;

  3. 析构函数中完成资源的清理,可以保证资源的正确初始化和释放

  4. 如果对象是用声明的方式在栈上创建局部对象,那么RAII机制就会正常工作,当离开作用域对象会自动销毁而调用析构函数释放资源。

2. 智能指针的特点

  1. 具有 RAII的思想

  2. 向指针一样,可以使其指向对象成员,并且重载了 * 和 -> 运算符

  3. 是对资源的一种管理,不是拥有

注:对于管理的理解

  1. 情况1:当我们使用普通指针进行管理动态内存时,往往会忘记释放内存,这时就会造成内存泄露,此时使用智能指针暂时保管这块内存,使用完了会自动调用析构,完成对资源的回收
  2. 情况2:如下示例,当抛出异常时,就会打断执行流,不会在执行下面的delete工作,这样的情况也是一种内存泄露,当使用智能指针就能很好的避免这样的情况
 void test()
{
    int*_ptr=new int(1);
    if(_ptr)
    {
        throw 1;
    }
    delete _ptr;
}

3. 模拟实现几种智能指针

3.1 AutorPtr
  本质上是一种管理权的转移,当我把我的空间交给你管理后,我自己就置为空,不在管理这块空间

几个缺点

  1. 不要使用auto_ptr保存一个非动态开辟空间的指针,因为在作用域结束的时候,会执行智能指针的析构函数,释放这块空间,但非动态的空间又无法释放;
  2. 不要使用两个auto_ptr指针指向同一个指针
  3. 不要使用auto_ptr指向一个指针数组,因为auto_ptr的析构函数所用的是delete而不是delete[],不匹配;
  4. 不要将auto_ptr储存在容器中,因为赋值和拷贝构造后原指针无法使用。
    注:什么情况下也别使用auto_ptr智能指针。

代码实现:

template<class T>
class AutorPtr
{
public:
    AutorPtr(T* ap)
      :_Ptr(ap)
    {}

    AutorPtr(AutorPtr<T>& ap)//管理权的转移
    {
        _Ptr = ap._Ptr;
        ap._Ptr = NULL;
    }

    AutorPtr& operator= ( AutorPtr<T>& ap)
    {
        if (this != &ap)
        {
            _Ptr = ap._Ptr;//管理权进行转移,自己的指针置为空
            ap._Ptr = NULL;
        }
        return *this;
    }

    ~AutorPtr()
    {
        printf("%p\n", _Ptr);
        delete _Ptr;
    }

    T& operator*()
    {
        return *_Ptr;
    }

    T* operator->()
    {
        return _Ptr;
    }
protected:

    T* _Ptr;
};

void TestAutorptr()
{
    AutorPtr<int> ap1(new int(10));
    AutorPtr<int> ap2 = ap1;
    //*ap1 = 100;//管理权已经转移,ap1管理的空间已经被ap2管理,所以导致空指针解引用
    *ap2 = 200;

    AutorPtr<int> ap3(new int(11));
    ap2 = ap3;
}

3.2 ScopedPtr

  1. 于autoptr最大的不同就是scoped_ptr没有给出拷贝构造和赋值运算符的重载运算符的定义,只给了private下的声明,即表明scoped_ptr智能指针无法使用一个对象创建另一个对象,
  2. 也无法采用赋值的形式。这无疑提升了智能指针的安全性,但是又存在无法“++”、“- -”这些操作,
  3. 与此同时多了“*”、“->”这两种操作。

代码实现:

template<typename T>
class ScopedPtr
{
public:
    explicit ScopedPtr(T *p = 0) 
        :mp(p)
    {}


    ~ScopedPtr()
    {
        delete mp;
    }

    void reset(T *p = 0)//重置
    {
        if (mp != p)
        {
            delete mp;
            mp = p;
        }
    }

    T &operator*() const
    {
        if (mp != 0)
            return *mp;
        else
            throw std::runtime_error("the pointer is null");
    }

    T * operator->() const
    {
        if (mp != 0)
            return mp;
        else
            throw std::runtime_error("the pointer is null");
    }

    T *get() const
    {
        return mp;
    }
    void swap(ScopedPtr &rhs)
    {
        T *temp = mp;
        mp = rhs.mp;
        rhs.mp = temp;
    }
private:
    ScopedPtr(const ScopedPtr& rhs);
    ScopedPtr &operator=(const ScopedPtr& rhs);//防拷贝,只声明不实现
    T *mp;
};


void TestScopedPtr()
{
    ScopedPtr<int> sp1(new int(3));
    //ScopedPtr<int> sp2(sp1);//防拷贝,不能拷贝
}

3.3 SharedPtr

  1. shared_ptr和以上二者的最大区别就是他维护了一个引用计数,用于检测当前对象所管理的指针是否还被其他智能指针使用(必须是shared_ptr管理的智能指针)
  2. 在析构函数时对其引用计数减一,判断是否为0,若为0,则释放这个指针和这个引用计数的空间。
  3. 其实,这个原理就和string类的浅拷贝是一样的。
  4. 这个类型的智能指针在一定程度上让我们的管理得到了很大的便捷和省心。
  5. 可以和其他共同管理这块空间的智能指针一起修改这块空间储存的值,达到“智能”的效果。

代码:

template<class T>
class SharedPtr
{
public:
    SharedPtr(SharedPtr<T>* sp,int* spcount)
        :_ptr(sp)
        , _pCount(spcount)
    {}

    SharedPtr(const SharedPtr<T>& sp)
    {
        _ptr = sp._ptr;
        _pCount = sp._pCount;
        ++(*pCount);
    }

    SharedPtr& opertaor=(const SharedPtr<T>& sp)
    {
        if (this != &sp)
        {
            if (--(*_pCount) == 0)
            {
                delete _ptr;
                delete _pCount;
            }
            _ptr = sp._ptr;
            _pCount = sp._pCount;
            ++(*pCount);
        }
        return *this;
    }

    ~SharedPtr()
    {
        if (--(*pCount) == 0)
        {
            delete _ptr;
            delete _pCount;
        }
    }
    T&operator*()
    {
        return *_ptr;
    }

    T*operator->()
    {
        return _ptr;
    }
private:
    T* _ptr;
    int* _pCount;
};

3.4 弱指针—weak_ptr

  先来说说弱指针的作用:用于专门解决SharedPtr循环引用的问题
  
两个问题:
 
  1. 什么是循环引用?

场景:

    struct ListNode
{
    SharedPtr<ListNode> _next;
    SharedPtr<ListNode> _prev;

    ListNode()
        :_prev(NULL)
        ,_next(NULL)
    {}

    ~ListNode()
    {
        cout << "delete ListNode" << endl;
    }
};

void TestSharePtrCycle()
{
    SharedPtr<ListNode> cur = new ListNode;
    SharedPtr<ListNode> next = new ListNode;
    cur->_next = next;
    next->_prev = cur;
}

分析场景:

这里写图片描述

  2. 什么是弱指针

弱指针是指当指针指向原来空间时,引用计数不在进行加1
注:不是管理和释放当前的指针,而是避免了一次引用计数

3.如何解决?

使用弱指针来负责cur->next 和next->prev的指向时,使其引用计数不在加1,这样两个节点的引用计数就是1,不会再增加,故此释放时,会将其节点进行释放

代码:

template<class T>
class WeakPtr;

template<class T>
class SharedPtr
{
    friend class WeakPtr<T>;
public:
    SharedPtr(T* ptr)
        :_ptr(ptr)
        , _pCount(new int(1))
    {}

    SharedPtr(const SharedPtr<T>& sp)
    {
        _ptr = sp._ptr;
        _pCount = sp._pCount;
        (*_pCount)++;
    }

    // sp1 = sp2;
    SharedPtr<T>& operator=(const SharedPtr<T>& sp)
    {
        if (this != &sp)
        {
            if (--(*_pCount) == 0)
            {
                delete _ptr;
                delete _pCount;
            }

            _pCount = sp._pCount;
            _ptr = sp._ptr;
            (*_pCount)++;
        }

        return *this;
    }

    ~SharedPtr()
    {
        //cout<<"~SharedPtr()"<<endl;
        if (--(*_pCount) == 0)
        {
            if (_ptr)
            {
                printf("ptr:%p\n", _ptr);
                delete _ptr;
            }
            delete _pCount;
        }
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

protected:
    T* _ptr;
    int* _pCount;
};

// 专门解决SharedPtr的循环引用
// 弱指针是指当指针指向原来空间时,引用计数不在进行加1,释放时不在等待,故此解决了循环引用的问题
// 不是管理和释放当前的指针,而是避免了一次引用计数
template<class T>
class WeakPtr
{
public:
    WeakPtr(SharedPtr<T>& sp)//可以将sharedptr的对像传给他,匿名对象
        :_ptr(sp._ptr)       //这里将sharedptr的对象给weakptr(弱指针)进行初始化,此时弱指针指向的是sharedptr的对象
    {}

    WeakPtr()
        :_ptr(NULL)
    {}

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }
protected:
    T* _ptr;
};




struct ListNode
{
    WeakPtr<ListNode> _next;
    WeakPtr<ListNode> _prev;

    //ListNode()
    //  :_prev(NULL)
    //  ,_next(NULL)
    //{}

    ~ListNode()
    {
        cout << "delete ListNode" << endl;
    }
};

void TestSharePtrCycle()
{
    SharedPtr<ListNode> cur = new ListNode;//使用弱指针
    SharedPtr<ListNode> next = new ListNode;
    cur->_next = next;//weakptr接收的是sharedptr的对象
    next->_prev = cur;
}

注:关于解决引用计数的具体步骤,可参考链接:
(https://blog.csdn.net/zhourong0511/article/details/80315961)

猜你喜欢

转载自blog.csdn.net/zhangye3017/article/details/80429780