c++:分析智能指针与发展历史

智能指针是RAII思想的一种产品,那么什么是RAII呢?
RAII:Resource Acquistion Is Initialization
资源分配就初始化,定义一个类来封装资源的分配和释放。
在构造函数里完成资源分配初始化,在析构函数里完成资源的清理。
简单来举例:

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

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

private:
    T* _ptr;
};

这个类就体现了RAII思想。
那么智能指针存在的意义是什么呢?c++用new/delete一样可以完成资源分配的初始化和清理。
问题就在于执行流的改变:

void Test()
{
    int* p = new int(1);
    if (p)
    {
        return;
    }
    delete p;
}

这段代码看似简单无误,其实已经存在了内存泄漏,因为return改变了执行流,导致无法释放。
改变执行流的语句还有很多:break,continue,gote,throw等,这些改变执行流的语句都可能导致已经new的空间无法delete,一旦代码非常多的时候,我们很难控制。所以使用智能指针是必要的!

首先,刚才写的AutoPtr这个类既然叫智能指针,那么它就要像个指针,我们有必要实现它的operator*和operator->。

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

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

这里写图片描述
(AA就是一个用来举例的,类有两个整形的成员变量)
要像个指针还要能赋值,但是当我赋值时: AutoPtr ap3(ap);
这里写图片描述
它崩溃了,原因很好理解,浅拷贝导致的两个对象的指向同一片空间,那么析构的时候这片空间必然会析构两次。还有一个问题,就是一个的改变会影响另一个。这些是我们不希望看到的,所以c++专家们在c++98给出了解决方案:auto_ptr
它的核心思想就是管理权转移,当一个指针把值赋给另一个时,它马上赋值为NULL。
以下是它的简单模拟实现:

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

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

    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;
    }

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

private:
    T* _ptr;
};

这里写图片描述
这样做就不会出问题,operator=同理。
但是这样做有很大的缺陷,ap = NULL;那么以后就不能使用它,很容易出错。
现在它已经被禁用。

在c++98提出auto_ptr后,由于有很大缺陷,有人忍的了继续使用,有人忍不了,所以非官方boost总结了一套方案它们分别是:
scoped_ptr shared_ptr weak_ptr
这里写图片描述
我来分别介绍:
1.scoped_ptr:防拷贝,简单粗暴

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

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

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

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

private:
    //1.定为私有 2.只声明不定义
    ScopedPtr(ScopedPtr<T>& sp);
    ScopedPtr<T>& operator=(ScopedPtr<T>& sp);

private:
    T* _ptr;
};

直接把拷贝构造函数和operator=定义为私有,而且只声明,那么不能赋值就不会出错,这是个简单粗暴的办法!

2.shared_ptr:用了引用计数的方法

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

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

    SharedPtr<T>& operator=(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*()
    {
        return *_ptr;
    }

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

    ~SharedPtr()
    {
        if (--(*_refCount) == 0)
        {
            delete _ptr;
            delete _refCount;
        }
    }

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

可以看出,_refCount用来计数,接下来分析它的赋值:
拷贝构造:
这里写图片描述
operator=:
它有不同的情况:
①自己给自己赋值:sp1 = sp1;或者sp2 = sp1;(sp2已经等于sp1)
②如果被赋值对象的*_refCount == 1,那么直接释放它,如果被赋值对象的*_refCount > 1,那就把它的_refCount减1.
这里写图片描述
然后析构函数根据*_refCount来决定是否清理对象,就不会存在浅拷贝的问题。
3.weak_ptr是用来解决shared_ptr的循环引用的问题,我在《c++:分析智能指针shared_ptr存在的循环引用的缺陷》一文会详细分析。

接下来在c++11,官方延用了boost总结的方法,需要注意的是,c++11把scoped_ptr改名为unique_ptr,并且没有写数组。

智能指针发展历史的总结:

这里写图片描述
boost和c++11有一些重名的智能指针,但是不影响使用,因为他们分别在boost::和std::

猜你喜欢

转载自blog.csdn.net/han8040laixin/article/details/78653515