天天看点

智能指针分析

为什么有智能指针?

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

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

调用的Test2()中如果有抛异常的话,就会无法释放p,造成内存泄漏,

这种情况就需要用智能指针来解决。

智能指针

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

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

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

RAII(Resource Acquisition Is Initialization)

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

所以智能指针其实就是一个类

智能指针(自动释放,可以像指针一样用)

1、自动释放

一个类里(构造,析构)

//RAII思想

2、像指针一样

通过运算符的重载实现(* 和->)

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

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

上面说了智能指针是什么,以及为什么存在这种智能指针,那么下面我们再来看看智能指针都有那些代表,以及它的发展

早期c++98 auto_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();
    *p1 = ;

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


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

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

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

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

    AutoPtr<int> ap3(new int());
    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());
    //ScopedPtr<int> sp2(sp1);错误使用
    ScopedPtr<int> sp3(new int());
    //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())
    {}

    ~SharedPtr()
    {
        if (--(*_refCount) == )
        {
            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) == )
            {
                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;
};
           

继续阅读