智能指针分析

为什么有智能指针?

在我们写代码时,经常会忘记释放掉动态开辟出来的内存,或者在我们的程序中,new和delete中间有如 throw/goto/return/break 这样引起执行流改变的语句时,就会造成没有释放资源,造成内存泄漏。

void Test1()
{
int* p=new int[100];
Test();
...
delete[] p;

调用的Test2()中如果有抛异常的话,就会无法释放p,造成内存泄漏,
这种情况就需要用智能指针来解决。


智能指针

所谓“智能指针”就是比一般的指针聪明,它可以智能/自动的释放掉指针所指向的动态资源空间。

那么这种智能指针是任何实现的呢?

在这里我们要提到一种思想

RAII(Resource Acquisition Is Initialization)

资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
所以智能指针其实就是一个类
智能指针(自动释放,可以像指针一样用)
1、自动释放
一个类里(构造,析构)
//RAII思想
2、像指针一样
通过运算符的重载实现(* 和->)

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

T* operator-> ()
{
return _ptr;
}
注意函数返回值类型

上面说了智能指针是什么,以及为什么存在这种智能指针,那么下面我们再来看看智能指针都有那些代表,以及它的发展
早期c++98auto_ptr管理权转移,带有缺陷
boost scoped_ptr守卫指针 防拷贝
scoped_arrary
shared_ptr共享指针引用计数,缺陷循环引用
shared_array
weak_ptr弱指针 不单独存在,辅助解决shared_ptr的循环引用问题
一、模拟实现AutoPtr指针
// 智能指针--类(AutoPtr指针)

//首先是个类
template<class T>
class AutoPtr
{
public:
    AutoPtr(T* ptr)//构造
        :_ptr(ptr)
    {}

    ~AutoPtr()//析构,清理资源
    {
        if(_ptr)
        { 
            printf("0x%p\n", _ptr);
            delete _ptr;
        }
    }

    // ap2(ap1)
    AutoPtr(AutoPtr<T>& ap)
        :_ptr(ap._ptr)
    {
        ap._ptr = NULL;
    }

    // ap1 = ap2
    AutoPtr<T>& operator=(AutoPtr<T>& ap)
    {
        if (this != &ap)
        {
            if(_ptr)
                delete _ptr;

            _ptr = ap._ptr;
            ap._ptr = NULL;
        }

        return *this;
    }

    // 运算符的重载
    T& operator*()
    {
        return *_ptr;
    }

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

private:
    T* _ptr;
};

struct AA
{
    int _a1;
    int _a2;
};

//写个函数对比测试一下
void Func ()
{
    int* p1 = new int(2);
    *p1 = 10;

    // RAII
    AutoPtr<int> ap1(new int(2));
    *ap1 = 10; //ap1.operator*() = 20;


    AA* p2 = new AA;
    p2->_a1 = 10;
    p2->_a2 = 20;

    AutoPtr<AA> ap2(new AA);
    ap2->_a1 = 10;
    ap2->_a2 = 20;
}

void TestAutoPtr()
{
    //Func();
    int* p1 = new int(2);
    int* p2 = p1;//没有释放内存,清理对象

    // 深拷贝
    AutoPtr<int> ap1(new int(1));
    AutoPtr<int> ap2 = ap1;

    AutoPtr<int> ap3(new int(2));
    ap2 = ap3;
}

分析:

与普通指针的异同

这里写图片描述

这里写图片描述

对比普通指针和智能指针,智能指针可以实现*/->两个功能,同时可以自动清理资源

管理权的转移

这里写图片描述
这里写图片描述
我们可以看到这里就体现出了auto_ptr这种智能指针的特点,也是它的缺陷,当另一个指针指向时,之前的指针就对它没有任何权利了,变为空指针,权利全部给新的指针,一个auto_ptr,只能管理一块空间(就像一个人只能有一个男朋友/女朋友,分手后,前女友就和你没有关系了,珍惜现女友)

二、模拟实现Scoped_ptr指针

这个指针的特点是防拷贝

如何实现防拷贝

1.只声明不实现

2.私有

template<class T>
class ScopedPtr 
{
public:
    // RAII
    ScopedPtr(T* ptr)
        :_ptr(ptr)
    {}

    ~ScopedPtr()
    {
        if(_ptr)
            delete _ptr;
    }

    // 像指针一样
    T& operator*()
    {
        return *_ptr;
    }

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

    // 防拷贝
    // 1.只声明不实现
    // 2.私有
private:
    ScopedPtr(const ScopedPtr<T>& sp);
    ScopedPtr<T>& operator=(const Scopedtr<T>& sp);
    //将拷贝构造和operator=封在类里,设为私有

private:
    T* _ptr;
};

void TestScopedPtr()
{
    ScopedPtr<int> sp1(new int(1));
    //ScopedPtr<int> sp2(sp1);错误使用
    ScopedPtr<int> sp3(new int(2));
    //sp1 = sp3;错误使用
}

三、Shared_ptr共享指针&Weak_ptr弱指针

(这两个指针要结合起来使用)
auto_ptr能共享空间,scoped_ptr不能拷贝,都很bug,但我们还有shared_ptr(当然它也有循环引用的问题,所以有弱指针辅助)


template<class T>
class SharedPtr
{
    public:
    SharedPtr(T* ptr = NULL)
        :_ptr(ptr)
        ,_refCount(new int(1))
    {}

    ~SharedPtr()
    {
        if (--(*_refCount) == 0)
        {
            cout <<"释放了 "<< endl;
            delete _ptr;
            delete _refCount;
        }
    }

    // sp2(sp1)
    SharedPtr(const SharedPtr<T>& sp)
        :_ptr(sp._ptr)
        ,_refCount(sp._refCount)
    {
        (*_refCount)++;
    }

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

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

        return *this;
    }

    T& operator*()
    {...}

    T* operator->()
    {...}

    int UseCount()
    {
        return *_refCount;
    }

private:
    T*   _ptr;
    int* _refCount;
};

上面的程序只是shared_ptr的部分功能的思想体现,不是真正的模拟shared_ptr指针,真正的shared_ptr在boost库里是很复杂的。

下面我们来说刚才写道的循环引用问题
struct ListNode
{
    int _data;
    SharedPtr<ListNode> _next;
    SharedPtr<ListNode> _prev;
    /*WeakPtr<ListNode> _next;
    WeakPtr<ListNode> _prev;*/

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

void TestSharedPtrRef()
{
    SharedPtr<ListNode> cur(new ListNode);
    SharedPtr<ListNode> next(new ListNode);

    cout<<cur.UseCount()<<endl;//1
    cout<<next.UseCount()<<endl;//1

    cur->_next = next;
    next->_prev = cur;

    cout<<cur.UseCount()<<endl;//2
    cout<<next.UseCount()<<endl;//2
}

这里写图片描述

这里的循环引用问题出现在了哪呢?

当我们调析构函数释放空间时,就出问题了
当我们要是放cur时,如果把cur直接释放,那么next的_prev就成了野指针。同理next节点也是一样的。 (其实就是当要释放cur节点时,先要清理掉它内部的指针,可是cur->next指向next,next->prev又指向cur,所以他们就成了一个死循环,一直就无法释放)

所以循环引用就是两个对象相互牵制了对方,从而导致两个对象谁都无法被释放,引发了内存泄露

这里写图片描述运行程序时就会发现程序死循环了

所以为了解决这种问题,我们就要用weak_ptr我们只需要将节点的_next和_prev计数就好,不需要把shared_ptr的指针也计数,就可以解决问题
template<class T>
class WeakPtr
{
public:
    WeakPtr()
        :_ptr(NULL)
    {}

    WeakPtr(const SharedPtr<T>& sp)
        :_ptr(sp._ptr)
    {}

    WeakPtr<T>& operator=(const SharedPtr<T>& sp)
    {
        _ptr = sp._ptr;
        return *this;
    }

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

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

private:
    T* _ptr;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值