智能指针——AutoPtr & ScopedPer & SharedPtr & WeakPtr

在说智能指针之前先说一下什么是RAII,RAII就是资源分配即初始化,这是一种思想,其最广泛的应用就是智能指针。

智能指针就是能自动完成资源的清理和释放。

为什么需要智能指针呢?因为有时候写的代码忘了进行指针所指向内存的清理工作,或者有时候代码的运行会提前return或者throw使得本来已经写好的清理代码未曾运行,比如下面的代码:

void Test()
{
    int* ptr = new int(0);

    //1.throw
    //2.return

    delete ptr;
}

针对这种情况,当然是有办法来处理的,但是处理起来先对比较麻烦,对于抛异常的throw,可以去捕获异常然后进行释放空间,而用智能指针进行处理时则显得比较容易,因为智能指针只用定义,释放内存与清理工作交给了智能指针的模板类内的析构函数去完成,如此一来就显得方便许多,所以在C++11中就有ScopedPer (uniquePtr), SharedPtr 和 WeekPtr供我们使用,而AutoPtr是早期的一种智能指针,但是因为它是一个失败的设计,所以C++11中并没有收录,这里让我们来简单的看看这四种智能指针的实现机制。

(1)AutoPtr

AutoPtr是利用管理权的转移实现的,也就是说一个指针只管理一个内存,当前内存有别的指针指向的时候,原来指向这块内存指针就指向NULL,以此来实现智能管理,防止两次析构同一块内存或者有内存没有释放这种情况。

下面简单的看一下这种AutoPtr的模板类的模拟实现:

template<class T>
class AutoPtr
{
public:
    AutoPtr()
        :_Ptr(new T)
    {}

    AutoPtr(AutoPtr<T>& ap)
        :_Ptr(ap._Ptr)
    {
        //将传过来的指针值赋值给当前对象,
        //并将传过来的指针值赋值为空,防止产生野指针
        ap._Ptr = NULL;
    }

    AutoPtr<T>& operator=(AutoPtr<T>& ap)
    {
        if (this != &ap)
        {
            //同样的处理
            delete _Ptr;
            _Ptr = ap._Ptr;
            ap._Ptr = NULL;
        }
        return *this;
    }
    //  *(解引用) 和 ->(指针操作符)的重载实现类外的指针使用
    T* operator->()
    {
        return _Ptr;
    }

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

    T* GetPtr()
    {
        return _Ptr;
    }
private:
    T* _Ptr;
};

说白了就是专门写了一个不用自己去调用的析构函数,所以这是一个失败的设计。

(2)ScoptedPtr( 或者叫UniquePtr )

ScoptedPtr是一种防拷贝的智能指针,或者说不允许进行拷贝和赋值更为准确,因为它是将它的拷贝构造和赋值运算符的重载在类内只声明,但是并不定义,所以在用别的指针进行赋值或者拷贝构造时,就会编译不过,也就保证了所写的代码中一个指针就只管理一块内存储,就不会发生内存泄漏等问题。

下面来看模拟实现的ScoptedPtr模板类代码:

template<class T>
class ScopedPtr
{
public:

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

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

    T* GetPtr()
    {
        return _Ptr;
    }
        //将拷贝构造和赋值运算符的重载,
        //在ScoptedPtr类内声明为保护,并且只声明不定义
protected:
        ScopedPtr(ScopedPtr<T>& ap);
        ScopedPtr& operator=(ScopedPtr<T>& ap);

        T* _Ptr;

};

(3)SharedPtr

SharedPtr是利用引用计数实现的智能指针,引用计数就是在类的成员变量中再定义一个指针指向一块内存,这块内存中保存着指向_Ptr这个指针所管理的内存的指针个数,也就是说记录着有多少指针共同管理这块内存,当其中有指针不再指向这块内存时,只将引用计数减去1,当引用计数的值为1时,再将这块内存清理和释放,如此一来就实现了内存的重复利用和忘记释放的问题。

下面看一下Sharedptr的模拟实现:

template<class T>
class SharedPtr
{
public:
    SharedPtr(T* ptr)
        :_Ptr(ptr)
        ,_refcount(new size_t(1))
    {}

    SharedPtr(SharedPtr<T>& ap)
        :_Ptr(ap._Ptr)
        ,_refcount(ap._refCount )
    {
        ++(*_refcount);
    }
    ~SharedPtr()
    {
        if (--(*_refcount) == 0)
        {
            delete _Ptr;
            delete _refcount;
        }
    }

    SharedPtr<T>& operator=(SharedPtr<T>& ap)
    {
        if (this != &ap)
        {
            Relase();
            _Ptr = ap._Ptr;
            _refcount = ap._refcount;
            ++(*_refcount);
        }
    }

    void Relase()
    {
        delete _Ptr;
        delete _refcount;
    }
private:
    T* _Ptr;                //智能指针
    size_t* _refcount;      //引用计数
};

★★SharedPtr的循环引用问题:

我们看到了SharedPtr的引用计数模拟实现代码,在析构函数中更可以清晰地看到,只有当引用计数的值为1时才会对这个指针指向的内存进行清理和释放,在实际使用当中,有时候在需要析构它的时候,它的引用计数并没有减为1,这就使得内存泄漏。

来看下面的代码(实际使用需要包头文件):

struct ListNode
{
    boost::shared_ptr<ListNode> _prev;   //前驱指针
    boost::shared_ptr<ListNode> _next;   //后继指针
};

void Test()
{
    boost::shared_ptr<ListNode> ptr1(new ListNode());   
    boost::shared_ptr<ListNode> ptr2(new ListNode());


    ptr1->next = ptr2;      //ptr1的后继指针指向ptr2
    ptr2->_prev = ptr1;     //ptr2的前驱指针指向ptr1

}

这里的代码产生了什么问题呢?我们来看一个简图:
这里写图片描述
我们看ptr1所指向的这块内存,由于是一个链表,所以在实际使用的过程中,它的后继节点会有指针指向它,也就是图中的绿色2号指针,如此一来它的引用计数就增加为2,而在析构的时候,由于ptr1是定义的智能指针,所以会自动完成清理工作,此时就是将它的引用计数值减去1,之后它的引用计数值变为1,对于ptr2指向的内存也是同样的道理,当ptr2 不在指向时,打的引用计数还是为1,如下图所示:

这里写图片描述

这里我们看到这块申请出来的内存就一直没有被析构,因为只有当它的引用计数值为1,并且要解除当前指针的指向关系时,它才会被析构,如图所示,左边的内存有ptr2->_prev指向,右边的内存有ptr1->_next指向,若想析构左边的内存,需要解除ptr2->_prev的指向,也就是ptr2->_prev被析构,若想析构右边的内存,需要解除ptr1->_next的指向,所以就陷入循环引用。

(4)WeakPtr

WeakPtr就是用来解决SharedPtr的循环引用问题的,它的实现原理是:只让指针指向,但是不增加引用计数。

也就是在使用的时候如下:

struct ListNode
{
    boost::weak_ptr<ListNode> _prev;   //前驱指针
    boost::weak_ptr<ListNode> _next;   //后继指针
};

总结:智能指针的只用要包含相关的头文件:
这里写图片描述
然后就可以像普通指针一样使用,而且不用自己现实的去完成清理和释放工作,非常方便。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值