智能指针

智能指针

 

我之前我们谈异常的时候,异常有异常安全的问题。

详细请看之前的异常  

https://blog.csdn.net/Amour_Old_flame/article/details/86759959

 

我们提出了RAII的解决方案,那么什么是RAII呢?

是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的
时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。

这样做有两个优势:

1.不需要我们显示的释放资源

2.用这种方式,对象所需要的资源在其生命周期时钟有效。

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr == nullptr) :_ptr(ptr)
	{}

	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};

这就是RAII,但是他并不能称为智能指针。因为他不能像指针一样使用,所以我们还要对 *  ->  进行重载。

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

这样才能称为一个简单的智能指针。

 

当然在我们的C++库中也提供了一些智能指针。、

C++库中的智能指针

 

1.atuo_ptr

C++98的库中提供的一个智能指针,他最大的特点就是他的拷贝构造

AutoPtr(AutoPtr<T>& ap)
 : _ptr(ap._ptr)
 {
    ap._ptr = NULL;  //赋值完之后前面的对象置空
 }

一旦发生拷贝,就将ap的资源转移到当前对象中,然后将ap与资源的联系断开

这样做会导致拷贝后ap为空,使得ap对象悬空了,如果再次通过ap对象访问资源就会导致程序崩溃。

 

auto_ptr主要采用管理权转移,拷贝时会使对象悬空,设计有缺陷。

 

 

2.unique_ptr

C++11中提供了unique_ptr

他的做法是将拷贝构造私有,不支持拷贝

private:
 // C++98防拷贝的方式:只声明不实现+声明成私有
 UniquePtr(UniquePtr<T> const &);
 UniquePtr & operator=(UniquePtr<T> const &);
 
 // C++11防拷贝的方式:delete
 UniquePtr(UniquePtr<T> const &) = delete;
 UniquePtr & operator=(UniquePtr<T> const &) = delete;

因为auto_ptr的拷贝有大的缺陷,所以unique_ptr粗暴的不能拷贝,但是却没有明显的缺陷。比auto_ptr更适合使用

 

 

3.shared_ptr

shared_ptr也是C++11提供的,这个智能指针就比较强大,没有大的缺陷,并且支持拷贝。

shared_ptr的原理主要是:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。就是有一个计数,当计数减为0的时候再去释放资源,这样就可以防止资源被多次释放。

主要有四点:

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

下面来简单的实现一下shared_ptr



template<class T>
class SharedPtr
{
  public:
    SharedPtr(T* ptr = NULL):_ptr(ptr),_pRefCount(new int(1)),_pMutex(new std::mutex)
    {
      if(_ptr == NULL)
      {
        _pRefCount = 0;
      }
    }
    SharedPtr(SharedPtr<T>& sp):_ptr(sp._ptr),_pRefCount(sp._pRefCount),_pMutex(sp._pMutex)
    {
      if(_ptr)
      {
        AddRefCount();
      }
    }

    SharedPtr<T>& operator=(SharedPtr<T>& sp)
    {
      if(_ptr != sp._ptr)
      {
        Release();
        _ptr = sp._ptr;
        _pRefCount = sp._pRefCount;
        _pMutex = sp._pMutex;
        if(_ptr)
        {
          AddRefCount();
        }
      }
      return *this;
    }

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

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

    int UseCount()
    {
      return *_pRefCount;
    }

    T* Get()
    {
      return _ptr;
    }

    int AddRefCount()
    {
      _pMutex->lock();
      (*_pRefCount)++;
      _pMutex->unlock();
      return *_pRefCount;
    }

    int SubRefCount()
    {
      _pMutex->lock();
      (*_pRefCount)--;
      _pMutex->unlock();                                                     
      return *_pRefCount;  
    }

    ~SharedPtr()
    {
      Release();
    }

  private:
    void Release()
    {
      if(_ptr && SubRefCount() == 0)
      {
        delete _ptr;
        delete _pRefCount;
      }
    }

  private:
    T* _ptr;
    int* _pRefCount;
    std::mutex*  _pMutex;
};

虽然shared_ptr看起来完美无缺,但是其实他还是有一个小的缺陷,那就是在特定场景下会发生循环引用的问题。

下面详细讲解一下

我们有两个几点node1 和 node2 分别有两个智能指针管理,他们的引用计数都变成了1.

node1->next = node2;

node2->prev = node1;

这样他们的引用计数都变成了2.

node1和node2析构,引用计数又减到1,但是node1->next 仍然指向node2,同时node2->prev指向node1。

此时当next和prev析构,node1和node2才会释放掉。

但是问题出现了next属于node1的成员,node1释放了,next才会析构,而node1由prev管理,prev属于node2成员,所以这就叫循环引用,谁也不会释放。都等着对方先放手,但谁也不会放手。这就是循环引用的问题。

 

那如何解决呢?

这时候就出现了shared_ptr的小保姆weak_ptr。 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。

weak_ptr没有引用计数,析构也不会释放资源不是RAII,他是专门用于解决shared_pre出现的循环引用问题。

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值