C++----浅析智能指针

18 篇文章 0 订阅
7 篇文章 0 订阅
背景

到目前为止,我们编写的程序中所使用的对象都有着严格定义的生命周期。全局对象在程序启动时分配,在程序结束时销毁。对于局部自动变量,当我们进入其定义所在的程序块时被创建,在离开块的时候销毁。局部static对象在第一次使用前分配,在程序结束时销毁。
除了自动和static对象外,C++还支持动态内存分配。动态分配的对象的生命周期与它们在何处创建是无关的,只有它们被显示释放时,这些对象才会销毁。

由于C++不像Java那样有自动回收内存机制,每次new出来的内存都要手动去delete,如果忘记delete就会造成内存泄漏。所以标准库定义了智能指针来管理动态分配的对象,当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。
智能指针其实不是指针而是一个对象。它的原理就是管理资源的RAII机制,先来看看什么叫RAII

RAII:全称Resource Acquisition Is Initialization

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

在Boost库中智能指针常见的有三个版本:

  • 资源管理权转移
  • 简单粗暴的防拷贝
  • 引用计数版本
资源管理权转移—–auto_ptr:

这个智能指针是98年应用到VS上的,来上一张图理解这个智能指针
这里写图片描述
auto_ptr的思想就是当我们需要调用拷贝构造或者赋值运算符实例化对象时,为了不析构多次,将原对象对资源的管理权交给新的对象并将原对象置空,来看看具体实现:

template<class T>
class AutoPtr
{
public:
      AutoPtr(T* ptr)
            :_ptr(ptr)
      {}
      AutoPtr(AutoPtr<T>& ap)
            :_ptr(ap._ptr)
      {
            ap._ptr = NULL;
      }
      AutoPtr<T>& operator=(const 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;
                  _ptr = NULL;
            }
      }
private:
      T* _ptr;
};

可以发现的是这种方式有一个很大的缺陷,当我们进行赋值或者拷贝构造之后,再去使用原对象程序就会崩溃,因为此时的原对象已经被置空了,对空指针解引用结果可想而知。所以在库中虽然还保留这种智能指针,但是却告诫人们在任何时候都不要使用auto_ptr

防拷贝—–scoped_ptr:

scoped_ptr是智能指针的第二个版本,它的实现方法非常简单,那就是不许你拷贝也不许你赋值,既然在拷贝或赋值的时候会出错那不拷贝不就可以了,来看看它的实现:

//防拷贝,简单粗暴
template<class T>
class ScopedPtr
{
public:
      ScopedPtr(T* ptr = NULL)
            :_ptr(ptr)
      {}
      ~ScopedPtr()
      {
            if (_ptr)
            {
                  delete _ptr;
                  _ptr = NULL;
            }
      }
      T& operator*()
      {
            return *_ptr;
      }
      T* operator->()
      {
            return _ptr;
      }
private:
      //只声明不实现,且声明为私有
      ScopedPtr(const ScopedPtr<T>& sp);
      ScopedPtr<T>& operator=(const ScopedPtr<T>& sp);
private:
      T* _ptr;
};

如果我们只是简单使用智能指针的话,scoped_ptr完全可以胜任,但在有些情况必须要拷贝或赋值的话,scoped_ptr就不再有用了,所以还有一个智能指针解决了这个问题

引用计数版本—–shared_ptr:

当进行拷贝或者赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。这是因为shared_ptr内部有一个引用计数,无论何时我们拷贝一个shared_ptr,计数器都会递增。当我们给shared_ptr赋予一个新值或者是shared_ptr被销毁时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它会自动释放自己所管理的对象。
话不多说,直接来看它的实现就懂了:

//引用计数,更实用更复杂
template<class T>
class SharedPtr
{
public:
      SharedPtr(T* ptr)
            :_ptr(ptr)
            , _count(new int(1))
      {}
      SharedPtr(SharedPtr<T>& sp)
            :_ptr(sp._ptr)
            ,_count(sp._count)
      {
            (*_count)++;
      }
      SharedPtr<T>& operator=(const SharedPtr<T>& sp)
      {
            if (*this != sp)
            {
                  if (_prt && --(*_count) == 0)
                  {
                        delete _ptr;
                        delete _count;
                  }
                  _ptr = sp._ptr;
                  _count = sp._count;
                  (*_count)++;
            }
            return *this;
      }
      ~SharedPtr()
      {
            if (--(*_count) == 0)
            {
                  delete _ptr;
                  delete _count;
                  _ptr = NULL;
                  _count = NULL;
            }
      }
      T& operator*()
      {
            return *_ptr;
      }
      T* operator->()
      {
            return _ptr;
      }
private:
      T* _ptr;
      int* _count;
};

但是绝对不要认为这个shared_ptr就完美了,它同样存在一个很大的缺陷,来看看下面的例子:

#include <iostream>
using namespace std;
#include <boost/shared_ptr.hpp>

struct ListNode
{
      shared_ptr<ListNode> _next;
      shared_ptr<ListNode> _prev;
      int _data;

      ~ListNode()
      {
            cout << "~ListNode()" << endl;
      }
};
int main()
{
      shared_ptr<ListNode> p1(new ListNode);
      shared_ptr<ListNode> p2(new ListNode);
      p1->_next = p2;//p1的_next节点指向p2
      p2->_prev = p1;//p2的_prev节点指向p1
      cout << "p1: " << p1.use_count() << endl;
      cout << "p2: " << p2.use_count() << endl;
      system("pause");
      return 0;
}

在这段代码中我们利用boost库中实现的shared_ptr定义了一个简单的双向链表,然后利用use_count()函数打印出当前对象的引用计数,可以看到都是2。说明p1和p2指针所管理的空间都有两个指针管理,但是这里会出现一些问题。
这里写图片描述
从图中可以看到此时每一个ListNode节点都有两个智能指针管理。当出了main函数作用域时,p1和p2就会调用自己的析构函数,然后各个节点的引用计数减一,然后_next和_prev也要调用自己的析构函数,但是这样就发生了一个情况

  • _next等着_prev做完自己的清理工作然后再清理_prev
  • _prev等着_next做完自己的清理工作然后再清理_next

但是谁都不会先做清理工作,这就是循环引用的问题
为了解决shared_ptr循环引用的问题,就有了weak_ptr

weak_ptr:

weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,还是刚刚的场景,变成下面这样就解决了循环引用的问题
要注意的是weak_ptr不能单独使用

struct ListNode
{
      weak_ptr<ListNode> _next;
      weak_ptr<ListNode> _prev;
      int _data;

      ~ListNode()
      {
            cout << "~ListNode()" << endl;
      }
};
int main()
{
      shared_ptr<ListNode> p1(new ListNode);
      shared_ptr<ListNode> p2(new ListNode);
      p1->_next = p2;//p1的_next节点指向p2
      p2->_prev = p1;//p2的_prev节点指向p1
      cout << "p1: " << p1.use_count() << endl;
      cout << "p2: " << p2.use_count() << endl;
      system("pause");
      return 0;
}

weak_ptr对象引用资源时不会增加引用计数,但是它能够通过lock()方法来判断它所管理的资源是否被释放。我们不能直接通过weak_ptr来访问资源。在需要访问资源的时候weak_ptr生成一个shared_ptr,shared_ptr能够保证在shared_ptr没有被释放之前,其所管理的资源是不会被释放的。创建shared_ptr的方法就是lock()方法。在这里不再深入谈论weak_ptr了,感兴趣的小伙伴可以自己了解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值