智能指针的发展史

1 .智能指针的发展史
智能指针的发展要从RAII说起,RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
(1)C++98提出auto _Ptr,auto_Ptr的设计是让两个指针指向同一块空间,但是这样会存在释放不当,出现程序崩溃的问题。
(2)在Boost库中设计了Scoped_Ptr,Shared_Ptr,Weak_Ptr这三种只能指针
(3)C++11经过进一步改进设计出了Unique_ptr,shared_ptr,Weak_ptr 这三种智能指针,Unique_ptr与相类似。
2.auto_Ptr/Scoped_Ptr/Shared_ptr/Weak_Ptr的设计思想、缺陷,以及它们各自的模拟实现?
(1) auto_Ptr的简单实现
auto_Ptr的几点注意事项:
auto_Ptr不能共享所有权
auto_Ptr不能指向数组
auto_Ptr不能作为容器的成员
不能通过赋值操作来初始化auto_Ptr

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

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

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

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

        return *this;
    }

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

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

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

protected:
    T* _ptr;
    };

可以看到一个很明显的缺陷 当使用了构造函数和拷贝构造函数时,原来的智能指针就失效了,使用权交给了新的智能指针,这种方式叫做管理权转移,任何时候只能有一个对象指向那块区域,当有看、两个指针指向同一块区域时,程序会崩溃
这里写图片描述
(2)Boost阶段 Boost阶段提出Scoped_Ptr 守卫指针 防拷贝

SharedPtr的提出主要是建立在AutoPtr的基础上,为了防止赋值和拷贝构造,可以用两种方法解决, 其一:只声明不实现
其二:声明成私有,这样,编译时就不会报错。
Scoped_Ptr的模拟实现

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

    ~Scoped_Ptr()
    {
        delete _ptr;
    }

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

    T* operator->()
    {
        return _ptr;
    }
private:
    Scoped_Ptr(const Scoped_Ptr<T>&);
    Scoped_Ptr<T>& operator=(const Scoped_Ptr<T>&);

protected:
    T* _ptr;
};

Shared_Ptr的 简单实现
相对 于AutoPtr,Shared _Ptr 解决了指针共享的所有权问题,Shared_Ptr引入了引用计数,可以被自由的拷贝和赋值。

template<class T, class Del>
class Shared_Ptr
{
public:
    Shared_Ptr()
        :_ptr(NULL)
        ,_refCount(NULL)
    {}

    Shared_Ptr(T* ptr = NULL, Del del)
        :_ptr(ptr)
        ,_refCount(new int(1))
        ,_del(del)
    {}

    ~Shared_Ptr()
    {
        if (--(*_refCount) == 0)
        {
            cout<<"delete"<<endl;
            delete _ptr;
            _del(_ptr);

            delete _refCount;
        }
    }

    Shared_Ptr(const Shared_Ptr<T, Del>& sp)
        :_ptr(sp._ptr)
        ,_refCount(sp._refCount)
    {
        ++(*_refCount);
    }

     sp2 = sp3
    Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp)
    {
        if (this != &s)
        if(_ptr != sp._ptr)
        {
            if (--(*_refCount) == 0)
            {
                delete _ptr;
                delete _refCount;
            }

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

        return *this;
    }


    int RefCount()
    {
        return *_refCount;
    }

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

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

    T* GetPtr() const
    {
        return _ptr;
    }

protected:
    T* _ptr;
    int* _refCount;

    Del _del;
};

Shared_ptr会带来循环引用问题。Weakptr 是用来解决Shared_ptr循环引用的缺陷问题,WeakPtr是一个弱指针,weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少.
WeakPtr的模拟实现
  

template<class T>
class Weak_Ptr
{
public:
    Weak_Ptr()
        :_ptr(NULL)
    {}

    Weak_Ptr(const Shared_Ptr<T>& sp)
        :_ptr(sp.GetPtr())
    {}

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

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

protected:
    T* _ptr;
};

虽然通过弱引用指针可以有效的解除循环引用, 但这种方式必须在程序员能预见会出现循环引用的情况下才能使用, 也可以是说这个仅仅是一种编译期的解决方案, 如果程序在运行过程中出
了循环引用, 还是会造成内存泄漏。
循环引用问题
你中有我,我中有你最后都导致双放都无法释放,最终导致内存泄漏,以双链表为例

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

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

};

void TestCycleRef()
{
    Shared_Ptr<ListNode> cur = new ListNode;
    Shared_Ptr<ListNode> next = new ListNode;

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

这里写图片描述
在这里,_next和_prev都是成员变量,第一块空间要想释放必须在第二块空间被释放之后才能释放,但由于第一块空间有一个节点指向第二块空间,所以只有第一块空间释放之后第二块空间才能释放,这样就带来了循环引用问题。

猜你喜欢

转载自blog.csdn.net/wyn126/article/details/76691329