c++:智能指针

更多内容点这里
C++学习 - 目录索引


1、为什么要有智能指针?

因为在代码中经常忘记释放动态开辟的内存资源,造成内存泄漏,并且在操作时要小心谨慎,谨防内存泄漏。因此产生了智能指针来动态的管理指针所指向的动态资源释放

1.1、追溯历史

  • C++98中一开始的智能指针是auto_ptr,auto_ptr是一种管理权的转移操作,但是auto_ptr依然存在缺陷,好多人是不推荐使用这个的
  • 在98到11这十几年时间中,产生了一个boost社区,里面的人为了弥补auto_ptr的缺陷,相继创造出来了《scoped_ptr/scoped_array:一种粗暴模式的防拷贝,推荐使用》,《shared_ptr/shared_array:在需要拷贝的地方增加了引用计数功能》,《weak_ptr:具有弱指针功能,主要配合解决shared_ptr循环引用》
  • C++11出现了《unique_ptr/shared_ptr》,它是参考的boost社区开发的智能指针

1.2、auto_ptr

  • 1、auto_ptr第一种实现方式
    作用:是一种将权限转移的方式(即:一个指针将一块空间的管理权限交给另一个指针)
    这里写图片描述
template<class T>
class AutoPtr
{
public:
    AutoPtr(T* ptr)
        :_ptr(ptr)
    {}
    ~AutoPtr()
    {
        printf("~ptr\n");
        delete _ptr;
    }
    //ap2(ap1)
    AutoPtr(AutoPtr<T>& ap)
    {
        _ptr = ap._ptr;
        ap._ptr = NULL;
    }
    //ap2 = ap1
    AutoPtr<T>& operator = (AutoPtr<T> &ap)
    {
        if (this != &ap)
        {
            delete _ptr;
            _ptr = ap._ptr;
            ap._ptr = NULL;
        }
        return *this;
    }
    T& operator*()
    {
        return *_ptr;
    }
    T& operator->()
    {
        return _ptr;
    }
private:
    T* _ptr;
};

void TestAutoPtr()
{
    AutoPtr<int> ap1(new int(10));
    AutoPtr<int>ap2(ap1);
    //*ap1 = 100;//此时ap1已经被置为空了,此时给*ap1赋值就会出现问题
    *ap2 = 200;

    AutoPtr<int>ap3(new int(10));
    ap3 = ap1;//此时ap3被置为NULL
    ap3 = ap2;//此时ap2被置为NULL

}
  • auto_ptr优点:实现智能管理指针指向动态资源的释放
  • auto_ptr缺点:就像拷贝构造函数和赋值运算符函数一样,比如拷贝构造函数,我们用ap2拷贝ap1,但是当拷贝完成后,发现ap1内部指针被置为空了,当我们再次调用ap1时,发现对象已经被释放了,所以程序就会崩溃。

  • auto_ptr的第二种实现方式
    成员变量增加一个bool值,如果该对象对于bool值为true就去析构该对象
    这里写图片描述

    但是这种情况依然存在缺点:当ap1出了作用域后,调用析构函数将资源释放,但是因为ap1和ap2共用一块地址空间,此时再调用ap2发现就会出现问题,显然ap2已经成为野指针,造成内存泄漏。

1.3、scoped_ptr

作用:这个指针以一种简单粗暴的模式直接抛弃的auto_ptr的缺点,这个指针直接不允许你拷贝和赋值

实现这种机制的方法有二种:

  • 1、将拷贝构造函数和赋值操作符函数声明为私有成员
  • 2、只声明构造函数和赋值操作符函数,不定义
    个人觉得将拷贝构造和赋值操作符声明为私有的成员更妥当一些。

1.3.1:代码实现

 template<class T>
 class ScopedPtr
 {
 public:
     ScopedPtr(T* ptr)
         :_ptr(ptr)
     {}
     ~ScopedPtr()
     {
         cout << "~ScopedPtr()" << endl;
     }
     T& operator*()
     {
         return *_ptr;
     }
     T& operator->()
     {
         return _ptr;
     }
 private:

     T* _ptr;
     ScopedPtr(const ScopedPtr<T>& sp);
     ScopedPtr<T>& operator = (const ScopedPtr<T> &sp);

 };

 void TestScopedPtr()
 {
     ScopedPtr<int>sp1(new int(3));
     ScopedPtr<int>sp2(sp1);

     ScopedPtr<int>sp3(new int(3));
     sp3 = sp1;
 }

这里写图片描述
用这种简单粗暴的方式防止你在外面调用这两种函数

1.4、shared_ptr

作用:像指针一样,但会记录有多少个shared_ptrs共同指向一个对象。这便是所谓的引用计数,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。这样指针可以共享一块内存,并且不用考虑内存泄漏问题。

优点:

  1. 多个指针指向同一个内存空间,释放后,不会造成内存泄漏
  2. 更加安全便捷的管理内存空间
  3. 可以安全的进行赋值操作符函数和拷贝构造含糊
  4. 多个指针共同管理一块空间,从而更加智能

1.4.1:代码实现

template<class T>
 class SharedPtr
 {
 public:
     SharedPtr(T* ptr, int *pcount)
         :_ptr(ptr)
         , _pCount(pcount)
     {
         cout << "SharedPtr" << endl;
     }
     ~SharedPtr()
     {
         if (--(*_pCount) == 0)
         {
             delete[] _ptr;
             delete[] _pCount;
             cout << "~SharedPtr" << endl;

             _ptr = NULL;
             _pCount = NULL;
         }
     }
     SharedPtr(const SharedPtr<T> & sp)
     {
         _ptr = sp._ptr;
         _pCount = sp._pCount;
         ++(*_pCount);
     }
     SharedPtr<T>& operator = (const SharedPtr<T>& sp)
     {
         if (this != &sp)
         {
             if (--(*_ptr) == 0)
             {
                 delete _ptr;
                 delete _pCount;
             }
             _ptr = sp._ptr;
             _pCount = sp._pCount;
             ++(*_pCount);
         }
         return *this;
     }
     T& operator*()
     {
         return *_ptr;
     }
     T* operator->()
     {
         return _ptr;
     }
 protected:
     T*_ptr;
     int* _pCount;
 };

缺点:

  1. 会产生循环引用的问题

    那么问题来了,什么是循环引用?

1.4.2、循环引用

eg:

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

    ListNode()
        :_prev(NULL)
        ,_next(NULL)
    {}

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

void TestSharePtrCycle()
{
    SharedPtr<ListNode> cur = new ListNode;
    SharedPtr<ListNode> next = new ListNode;
    cur->_next = next;
    next->_prev = cur;
}

分析:

这里写图片描述

因此为了解决这个问题,引用了weak_ptr。

1.4.3、weak_ptr

作用:weak_ptr(弱指针),专门解决shared_ptr循环引用问题。只引用不计数。

代码演示:

template<class T>
 class WeakPtr;

 template<class T>
 class SharedPtr
 {
     friend class WeakPtr<T>;
 public:
     SharedPtr(T* ptr)
         :_ptr(ptr)
         , _pCount(new int(1))
     {
         cout << "SharedPtr" << endl;
     }

     SharedPtr(const SharedPtr<T>& sp)
     {
         _ptr = sp._ptr;
         _pCount = sp._pCount;
         (*_pCount)++;
     }

     // sp1 = sp2;
     SharedPtr<T>& operator=(const SharedPtr<T>& sp)
     {
         if (this != &sp)
         {
             if (--(*_pCount) == 0)
             {
                 delete _ptr;
                 delete _pCount;
             }

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

         return *this;
     }

     ~SharedPtr()
     {
         //cout<<"~SharedPtr()"<<endl;
         if (--(*_pCount) == 0)
         {
             if (_ptr)
             {
                 printf("ptr:%p\n", _ptr);
                 delete _ptr;
             }
             delete _pCount;
         }
     }

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

     T* operator->()
     {
         return _ptr;
     }
     int Count()
     {
         return *_pCount;
     }
 protected:
     T* _ptr;
     int* _pCount;
 };

 template<class T>
 class WeakPtr
 {
 public:
     WeakPtr(SharedPtr<T>& sp)//这里将sharedptr的对像传给他,匿名对象
         :_ptr(sp._ptr)   //这里将sharedptr的对象给weakptr(弱指针)进行初始化,此时弱指针指向的是sharedptr的对象
     {}

     WeakPtr()
         :_ptr(NULL)
     {}

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

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


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

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

 void TestSharePtrCycle()
 {
     SharedPtr<ListNode> cur = new ListNode;//使用弱指针weak_ptr
     SharedPtr<ListNode> next = new ListNode;
     //使用弱指针来负责cur->next 和next->prev的指向,使其引用计数不在加1
     cur->_next = next;//weakptr接收的是sharedptr的对象,所以可以相互指向
     next->_prev = cur;

     cout << "cur:" << cur.Count() << endl;
     cout << "next:" << next.Count() << endl;
 }

因此:弱指针是指当指针指向原来空间时,引用计数不在进行加1,释放时可直接释放,因此解决了循环引用的问题,它不是为了管理指针,而是为了配合shared_ptr避免了一次引用计数。

猜你喜欢

转载自blog.csdn.net/triorwy/article/details/80383972