C++ 智能指针(及循环引用问题)

何所谓智能指针?
所谓的智能指针就是智能/自动化的管理指指针所指向的动态资源的释放。
智能指针的产生是由于C++中没有内存的自动回收机制,每次new出来的空间都需要delete,而个别情况下,总是无法及时的delete,或者异常导致程序提早退出,造成内存泄漏,故而产生智能指针。

智能指针的发展可分为三个阶段
(1)auto_ptr c++98
(2)scoped_ptr/shared_ptr/weak_ptr boost库
(3)unique_ptr/shared_ptr/weak_ptr c++11

一、auto_ptr
auto_ptr实际上是管理权限的转移,此设计本身带有缺陷,建议什么情况下都不要使用。

#include<iostream>
using namespace std;

template <typename T>
class Auto_ptr
{
public:
    Auto_ptr(T* ptr = 0)
        :_ptr(ptr)
    {}

    Auto_ptr(Auto_ptr<T>& p)
        :_ptr(p._ptr)
    {
        p._ptr = NULL;
    }

    ~Auto_ptr()
    {
        delete _ptr;
    }

    Auto_ptr<T>&operator=(Auto_ptr<T>&p)
    {
        if (_ptr != p._ptr)
        {
            if (_ptr)
            {
                delete _ptr;
            }
            _ptr = p._ptr;
            p._ptr = NULL;
        }
    }

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

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

int main()
{
    Auto_ptr<int> p1(new int(10));
    cout << *p1 << endl;

    Auto_ptr<int> p2(p1);   //p1拷贝构造出p2
    cout << *p1 << endl;    //再次访问p1的空间

    system("pause");
    return 0;
}

auto_ptr的核心思想就是管理权限的转移,通过赋值运算符的重载或拷贝构造时,把p1置空,即将p1开辟的空间的管理权限转交给p2后,我们进行简单操作时并不会有问题:
这里写图片描述

但管理权限转交给p2后,我们再去访问旧指针时,就会出错,这并不是我们所期待的。这里写图片描述

二、scoped_ptr
scoped_ptr又叫 守卫指针,用来防止拷贝,可以解决Auto_ptr的问题。
解决的方法:
(1)拷贝构造和赋值运算符的操作可以只声明,不定义。
(2)将声明为私有,防止定义。

template<class T>
class Scoped_ptr
{
public:
    Scoped_ptr(T*ptr)
        :_ptr(ptr)
    {}

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

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

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

private:
    Scoped_ptr(const Scoped_ptr<T>& ptr);
    Scoped_ptr<T>& operator=(Scoped_ptr& ptr);

private:
    T* _ptr;
};

int main()
{
    Scoped_ptr<int>p1(new int(1));
    Scoped_ptr<int>p2(p1);
    return 0;
}

这里写图片描述
指针功能明显受到限制,后来人们就不用了;于是更合理的智能指针诞生了。

三、shared_ptr
当auto_ptr和scoped_ptr都不能满足我们实际需求时,这是我们引入shared_ptr,它的主要思想是引入了引用计数,使得多个指针可以指向同一块空间,当最后一个指针释放时才真正释放这块空间。

template<class T>
class Shared_ptr
{
public:
    Shared_ptr(T* ptr)
        :_ptr(ptr)
        ,_count(new int(1))  
    {}

    ~Shared_ptr()
    {
        Release();
    }

    Shared_ptr(Shared_ptr<T>& p)
        :_ptr(p._ptr)
        , _count(p._count)
    {
        (*_count)++;
    }

    Shared_ptr<T>operator=(Shared_ptr<T>& p)
    {
        if (_ptr != p._ptr)
        {
            Release();  //先释放*this中的指针指向的空间
            _ptr(p._ptr);   
            _count(p._count);
            (*_count)++;
        }
        return *this;
    }

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

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

    void Release()
    {
        if (--(*_count) == 0)
        {
            delete _ptr;
            delete _count;
            _ptr = NULL;
            _count = NULL;
        }
    }

    int GetCount()
    {
        return *_count;
    }

    T* GetPtr()const    //获取指针
    {
        return _ptr;
    }
private:
    T* _ptr;
    int* _count;
};

int main()
{
    Shared_ptr<int> p1(new int(1));
    Shared_ptr<int> p2(p1);
    *p1 = 10;
    cout << *p1 << endl;
    cout << p1.GetCount() << endl;
    cout << p2.GetCount() << endl;

    system("pause");
    return 0;
}

这里写图片描述
上面的代码虽然简单的实现引用计数版的简化版智能指针Shared_ptr;但是存在着以下问题:
(1)引用计数更新存在着线程安全。
(2)循环引用的问题。
(3)定制删除器。

四、循环引用:
首先我们来举一个循环引用的例子:

template <typename T>
class Node
{
public:
    Node(const T& value)
        :_pPre(NULL)
        , _pNext(NULL)
        , _value(value)
    {
        cout << "Node()" << endl;
    }
    ~Node()
    {
        cout << "~Node()" << endl;
        cout << "this:" << this << endl;
    }

    shared_ptr<Node<T>> _pPre;
    shared_ptr<Node<T>> _pNext;
    T _value;
};

void Funtest()
{
    shared_ptr<Node<int>> sp1(new Node<int>(1));
    shared_ptr<Node<int>> sp2(new Node<int>(2));

    cout << "sp1.use_count:" << sp1.use_count() << endl;
    cout << "sp2.use_count:" << sp2.use_count() << endl;

    sp1->_pNext = sp2;
    sp2->_pPre = sp1;

    cout << "sp1.use_count:" << sp1.use_count() << endl;
    cout << "sp2.use_count:" << sp2.use_count() << endl;
}
int main()
{
    Funtest();
    system("pause");
    return 0;
}

这里写图片描述
我们可以看出,并没有调用析构函数,也就是没有对空间进行释放。
从上面shared_ptr的实现中我们知道了只有当引用计数减减之后等于0,析构时才会释放对象,而上述情况造成了一个僵局,那就是析构对象时先析构sp2,可是由于sp2的空间sp1还在使用中,所以sp2.use_count减减之后为1,不释放,sp1也是相同的道理,由于sp1的空间sp2还在使用中,所以sp1.use_count减减之后为1,也不释放。sp1等着sp2先释放,sp2等着sp1先释放,二者互不相让,导致最终都没能释放,内存泄漏。

解决方案:使用弱指针—->weak_ptr(不增加引用计数)

template <typename T>
struct Node
{
public:
    Node(const T& value)
        :_value(value)
    {
        cout << "Node()" << endl;
    }
    ~Node()
    {
        cout << "~Node()" << endl;
    }

    weak_ptr<Node<T>> _pPre;
    weak_ptr<Node<T>> _pNext;
    T _value;
};
void Funtest()
{
    shared_ptr<Node<int>> sp1(new Node<int>(1));
    shared_ptr<Node<int>> sp2(new Node<int>(2));

    cout << "sp1.use_count:" << sp1.use_count() << endl;
    cout << "sp2.use_count:" << sp2.use_count() << endl;

    sp1->_pNext = sp2;
    sp2->_pPre = sp1;

    cout << "sp1.use_count:" << sp1.use_count() << endl;
    cout << "sp2.use_count:" << sp2.use_count() << endl;
}
int main()
{
    Funtest();
    system("pause");
    return 0;
}

这里写图片描述

至于weak_ptr具体是如何工作的我们可以点击链接,看大神的解释:
Boost智能指针——weak_ptr

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fly_bit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值