智能指针剖析

C++11中引入了智能指针包括auto_ptr、shared_ptr、weak_ptr、unique_ptr

我们在写C++程序时,动态内存需要自己维护,动态开辟的空间,在出函数作用域或者程序正常退出前必须释放,否则会造成内存泄露

void fun() {
   T *t = new T();
   //do something
   delete t;
   return;
}

在do something时,如果出现了异常或者其他原因,导致提前结束离开函数,那么new出来的内存将不会被释放,而使用智能指针管理这块内存时,会通常在智能指针本身被析构时,正确释放new出来的空间。

引入智能指针的作用是为了保证在出现异常时,也能保证堆内存会被释放掉

在C++中,通过RALL(资源分配)实现智能指针。

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

auto_ptr

通过资源转移机制来实现的。

class Autoptr
{
public:
     Autoptr(T* ptr = NULL)
          :_ptr(ptr)
     {
          _ptr = ptr;
     }
     Autoptr(Autoptr<T>&p)
     {
          _ptr = p._ptr;
          p._ptr = NULL;
     }
     Autoptr<T>&operator=(Autoptr<T>&p)
     {
          if (this != &p)
          {
              if (_ptr)
              {
                   delete _ptr;
              }
              _ptr = p._ptr;
              p._ptr = NULL;
          }
          return *this;
     }
     ~Autoptr()
     {
          if (_ptr != NULL)
          {
              delete _ptr;
              _ptr = NULL;
          }
     }
     T& operator*()
     {
          return *_ptr;
     }
     T operator->()
     {
          return _ptr;
     }
private:
     T* _ptr;
};

在这种机制下,我们实现的拷贝构造和赋值运算符重载,将原有资源交给新的对象来管理,同时将原有空间的管理权限交出去。

  • 优点是很好的解决在拷贝构造函数和赋值运算符完成后将两个指针同时指向一块空间
  • 缺点是在销毁时,容易让空间销毁两次,造成程序奔溃。

那么怎么处理这种问题呢?(防拷贝)

  • 1.我们可能会想到,将拷贝构造函数和赋值运算符重载给为私有的,但是类的友元函数还是可以在类外访问私有成员的,所以不可行。
  • 2.或者是将拷贝构造函数和赋值运算符重载只给声明,不定义,但是类外是可以定义这两个数的,所以不可行。
  • 3.将前两点结合,将拷贝构造哈数和赋值重载函数给成私有的,并只给出声明,可行。
template<class T>
class Autoptr
{
public:
     Autoptr(T* ptr = NULL)
          :_ptr(ptr)
     {
          _ptr = ptr;
     }
     ~Autoptr()
     {
          if (_ptr != NULL)
          {
              delete _ptr;
              _ptr = NULL;
          }
     }
     T& operator*()
     {
          return *_ptr;
     }
     T operator->()
     {
          return _ptr;
     }
private:
     Autoptr(Autoptr<T>&p);
     Autoptr<T>&operator=(Autoptr<T>&p);
private:
     T* _ptr;
};

auto_ptr是不建议使用的。

shared_ptr

shared_ptr和auto_ptr实现基本是一样的,s**hared_ptr维护了一个足够大的引用计数,保证在引用计数归为0时正确释放堆中的内存**。share_ptr使用的引用计数类是RefCount.

我们用以下的代码剖析一下循环引用的底层是什么?

struct ListNode{
   shared_ptr<ListNode> _pre;
   shared_ptr<ListNode> _next;
   int data;

   ~ListNode()
   {}
};
void test()
{
    shared_ptr<ListNode> pa(new ListNode());
    shared_ptr<ListNode> pb(new ListNode());

    pa->next = pb;
    pb->next = pa;
}

这里写图片描述
首先在栈中构造了两个智能指针pa,pb,分别管理一块具有双向链表结点的空间。各自的引用计数为1。pa->next=pb;pb->pre=pa;使得各自的引用计数变为2。所以在析构pa和pb时,引用计数都不为0,于是产生了循环引用,导致内存泄露。

对于循环引用这个问题,引出了weak_ptr。

weak_ptr

weak_ptr不会增加shared_ptr的引用计数,析构时会正确释放内存。
这里写图片描述
当我们释放pb指向的空间时,pb引用计数结构体中use-1,weak-1,pa的引用计数结构体中weak-1.

shared_ptr是用来共享内存的,weak_ptr是用来避免循环引用的。使用weak_ptr时需要用lock检查weak_ptr保存的的指针是否有效。

unqiue_ptr

是一种特殊的shared_ptr,其拷贝构造函数和运算符重载是删除的,意味着我们初始化一个unique_ptr,它会独占内存。

猜你喜欢

转载自blog.csdn.net/zwe7616175/article/details/80991153