天天看點

C++ -- 智能指針(自己模拟實作簡單的智能指針)

上一篇文章介紹了智能指針的基本概念及boost庫裡基本的智能指針,這裡主要模拟實作庫裡面的智能指針(簡單實作)。https://blog.csdn.net/xu1105775448/article/details/80625936

auto_ptr

1.auto_ptr具有RAII和像指針一樣的特點。

2.模拟實作:

template<class T>
class Auto_Ptr
{
public:
      //RAII
      Auto_Ptr(T* ptr)
           :_ptr(ptr)
      {}

      ~Auto_Ptr()
      {
           delete _ptr;
      }

      //像指針一樣
      T& operator*()
      {
           return *_ptr;
      }

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

void TestAutoPtr()
{
      int *ptr = new int();
      Auto_Ptr<int> p(ptr);
      /**p = 20;
      cout << *p << endl;*/
      Auto_Ptr<int> p1(p);

}
           

3.但是如果隻寫上面的代碼就會有一個問題,當我們想将一個AutoPtr指派(或拷貝構造)給另一個AutoPtr,我們沒寫它的拷貝構造和指派運算符重載,就會調用編譯器的,這是淺拷貝,就會導緻空間被多次釋放。

4.auto_ptr思想是:管理權的轉移。也就是當發現有一個auto_ptr對象a1指派給另一個對象a2或者拷貝構造a2,那麼a1獲得的資源就交給a2,a2來管理a1指向的那塊空間,a1就指向NULL,這樣就保證了每塊空間隻有一個auto_ptr對象指向它,就不會有空間被釋放多次的問題。

5.自己模拟實作完善版本的:

template<class T>
class AutoPtr
{
public:
      //構造函數獲得資源
      AutoPtr(T* ptr)
           :_ptr(ptr)
      {}

      //管理權轉移
      AutoPtr(AutoPtr<T>& ap)   //參數不能加const
           :_ptr(ap._ptr)
      {
           ap._ptr = NULL;
      }
      AutoPtr<T>& operator=(AutoPtr<T>& ap)
      {
           if (this != &ap)
           {
                 //先釋放自己的
                 delete _ptr;
                 _ptr = ap._ptr;
                 ap._ptr = NULL;
           }
           return *this;
      }
      //析構函數清理資源
      ~AutoPtr()
      {
           if (_ptr != NULL)
           {
                 delete _ptr;
                 printf("0X%p\n", _ptr);
           }
      }

      //像指針一樣
      T& operator*()
      {
           return *_ptr;
      }

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

void TestAutoPtr()
{
      AutoPtr<int> ap1(new int());
      AutoPtr<int> ap2(new int());
      ap1 = ap2;
}
           

6.auto_ptr管理權的另一種實作方式:(增加一個bool類型的owner,作為一個标志,如果我管理這塊空間,那麼我的owner就為true,沒有管理就為false);但是這種方式沒有上面直接将指向的空間置空好,若ap1拷貝構造ap2,那麼這裡的所有權就在ap2上,即ap2的owner為true,ap1的owner為false,但是如果ap2的生命周期比ap1短就有問題。例如函數傳參時,當用ap2傳給一個函數fun,而fun得參數為一個auto_ptr對象ap,就會将ap2得管理權轉移給ap,當ap出了作用域就會調用其析構函數,就會将ap指向的空間釋放掉,就會使得再想通路ap2就會出錯。

template<class T>
class AutoPtr
{
public:
      //構造函數獲得資源
      AutoPtr(T* ptr)
           :_ptr(ptr)
           ,_owner(true)
      {}

      //管理權轉移
      AutoPtr(AutoPtr<T>& ap)
           :_ptr(ap._ptr)
      {
           ap._owner = false;
           _owner = true;
      }
      AutoPtr<T>& operator=(AutoPtr<T>& ap)
      {
           if (this != &ap)
           {
                 //先釋放自己的
                 delete _ptr;
                 _ptr = ap._ptr;
                 ap._owner = false;
                 _owner = true;
           }
           return *this;
      }
      //析構函數清理資源
      ~AutoPtr()
      {
           if (_owner == true)
           {
                 delete _ptr;
                 printf("0X%p\n", _ptr);
           }
      }

      //像指針一樣
      T& operator*()
      {
           return *_ptr;
      }

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

7.但是autoptr有個很大的缺點,當将ap1的管理權交給ap2後,就會使得ap1指向一塊空的空間,當需要解引用ap1時,就是解引用空指針。

scoped_ptr

1.scoped_ptr也要有RAII和像指針一樣的特點,它采用防拷貝的方式。(防拷貝:将拷貝構造和指派運算符是聲明實作,并且聲明為私有,因為我們自己聲明,就不會調用系統自動生成的,而且如果隻聲明,就會有人在類外定義,是以必須聲明為私有)

2.模拟實作:

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

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

      //像指針一樣
      T& operator*()
      {
           return *_ptr;
      }

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

public:
      //将拷貝構造和指派運算符重載聲明為私有
      ScopedPtr(const ScopedPtr<T>& sp);
      ScopedPtr<T> operator=(const ScopedPtr<T>& sp);

protected:
      T* _ptr;
};

void TestScopedPtr()
{
      ScopedPtr<int> sp(new int());
      ScopedPtr<int> sp1(sp);
}
           

scoped_array

1.scoped_ptr用于管理new單個對象的空間的釋放,而scoped_array用于管理new多個對象的釋放(即調用new[]),而且scoped_array裡面不支援operator*和operator->。對資料的通路采用[](像數組一樣,通過下标的形式)。

2.模拟實作:

template<class T>
class ScopedArray
{
public:
      //RAII
      ScopedArray(T* ptr)
           :_ptr(ptr)
      {}

      ~ScopedArray()
      {
           if (_ptr)
           {
                 delete[] _ptr;
           }
      }

      T& operator[](size_t pos)
      {
           return _ptr[pos];
      }

public:
      ScopedArray(const ScopedArray<T>& sp);
      ScopedArray<T> operator=(const ScopedArray<T>& sp);

protected:
      T* _ptr;
};
void TestScopedArray()
{
      ScopedArray<int> sp1(new int[]);
}
           

shared_ptr

1.shared_ptr為共享指針,意味我們共同指向一塊空間;裡面采用引用計數,當有别的shared_ptr指向我這塊空間時,就增加引用計數,當引用計數減為0的時候,才釋放這塊空間。

2.模拟實作:

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

      SharedPtr(const SharedPtr<T>& ap)
           :_ptr(ap._ptr)
           , _pCount(ap._pCount)
      {
           ++(*_pCount);
      }

      SharedPtr<T>& operator=(const SharedPtr<T>& ap)
      {
           if (_ptr != ap._ptr)
           {
                 if (--(*_pCount) == )
                 {
                      delete _ptr;
                      delete _pCount;
                 }
                 _ptr = ap._ptr;
                 _pCount = ap._pCount;
                 ++(*_pCount);
           }
           return *this;
      }
      ~SharedPtr()
      {
           if (--(*_pCount) == )
           {
                 delete _ptr;
                 delete _pCount;
                 cout << "~SharedPtr()" << endl;
           }
      }

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

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


void TestSharedPtr()
{
      SharedPtr<int> sp1(new int());
      SharedPtr<int> sp2(sp1);
      sp1 = sp2;
}
           

3.但是shared_ptr會出現一個問題,就是循環引用,就會導緻引用計數一直無法減到0,就會導緻空間沒有釋放。例如如下代碼:

template<class T>
class SharedPtr;

template<class T>
struct ListNode
{
      T _data;
      SharedPtr<ListNode<T>> _next;
      SharedPtr<ListNode<T>> _prev;

      ListNode(T data)
           :_data(data)
           , _next(NULL)
           , _prev(NULL)
      {}
};
//中間部分與上面SharedPtr代碼一樣
void TestSharedPtr()
{
      SharedPtr<ListNode<int>> sp1(new ListNode<int> (10));
      SharedPtr<ListNode<int>> sp2(new ListNode<int>(20));
      sp1->_next = sp2;
      sp2->_prev = sp1;
}
           

分析:

  • 有兩個share_ptr對象sp1和sp2,剛開始sp1和sp2的引用計數都為1;
  • 将sp2指派給sp1->_next的時候,sp1->_next的引用計數變為2且sp2的引用計數也變為2;将sp1指派給sp2->_prev時,sp2->_prev的引用計數變為2且sp1的引用計數也變為2。
  • 又因為sp1裡的_next和_prev兩個指針的釋放依賴于sp1,而sp1的釋放又依賴于sp2->_prev,sp2的_prev的釋放又依賴于sp2,sp2又依賴于sp1的_next的釋放,這就是我的釋放依賴于你,你的釋放依賴于我,就會導緻陷入死循環中,導緻空間沒有被釋放,進而就會産生記憶體洩露的問題。
    C++ -- 智能指針(自己模拟實作簡單的智能指針)
    4.shared_ptr采用weak_ptr解決循環引用的問題,将會出現循環引用的shared_ptr通過weak_ptr的構造函數儲存下來,并且weak_ptr裡面不增加引用計數。但是它不是嚴格意義上的智能指針,它是輔助shared_ptr的使用,為了解決shared_ptr循環引用的問題。
template<class T>
class WeakPtr
{
public:
      WeakPtr(SharedPtr<T> sp)
           :ptr(sp._ptr)
      {}

      WeakPtr(const WeakPtr<T>& sp)
           :ptr(sp.ptr)
      {}

      WeakPtr<T>& operator=(const WeakPtr<T>& wp)
      {
           if (this != &wp)
           {
                 ptr = wp.ptr;
           }
           return *this;
      }

protected:
      T* ptr;
};
           

shared_array

1.shared_ptr用于管理單個對象的釋放,而shared_array用于管理多個對象的釋放。對于scoped_array來說,它不支援*和->,它支援[]的形式通路。

2.模拟實作:

template<class T>
class SharedArray
{
public:
      SharedArray(T* ptr)
           :_ptr(ptr)
           , _pCount(new int())
      {}

      SharedArray(const SharedArray<T>& sa)
           :_ptr(sa._ptr)
           , _pCount(sa._pCount)
      {}

      SharedArray<T>& operator=(const SharedArray<T>& sa)
      {
           if (_ptr != sa._ptr)
           {
                 if (--(*_pCount) == )
                 {
                      delete[] _ptr;
                      delete _pCount;
                 }
                 _ptr = sa._ptr;
                 _pCount = sa._pCount;
                 ++(*_pCount);
           }
           return *this;
      }

      T& operator[](size_t pos)
      {
           return _ptr[pos];
      }

      ~SharedArray()
      {
           if (--(*_pCount) == )
           {
                 delete[] _ptr;
                 delete _pCount;
           }
      }
protected:
      T *_ptr;
      int* _pCount;
};
           

定制删除器

1.對于C++11來說,它的庫裡面不包含shared_array和scoped_array,是以當new[]時,必須要依靠别的方式才能按照delete[]的形式釋放。C++11和boost裡面都包含定址删除器,當我以何種方式動态開辟的空間,我就要以什麼樣的方式去釋放空間。(對于new[]就要delete[],對于fopen就要fclose,對于lock就要unlock)

2.當自己模拟實作的時候,對于shared_ptr來說,可以給它兩個模闆參數,一個表示指針的類型,一個表示采用何種方式删除。

3.定址删除器就是一個仿函數,通過函數對象調用重載的operator()函數,裡面是delete,就調用delete,裡面是delete[]就是delete[]。

4.模拟實作:

(1)首先包含仿函數,這裡定義了3個仿函數,一個是專門用來進行delete,一個用來delete[],一個用來fclose;

template<class T>
struct Delete
{
      void operator()(T* ptr)
      {
           delete ptr;
      }
};

template<class T>
struct DeleteArray
{
      void operator()(T* ptr)
      {
           delete[] ptr;
      }
};

struct Fclose
{
      void operator()(FILE* fp)
      {
           fclose(fp);
      }
};
           

(2)前面實作的SharedPtr進一步改進,增加一個模闆參數,用來管理指針的釋放。增加一個模闆參數D,給一個預設參數為Delete,當我們不适用第二個模闆參數表示采用delete的方式,當我們給定第二個模闆參數之後,我們使用哪個模闆參數,就調用哪個仿函數。

template<class T,class D = Delete<T>>
class SharedPtr
{
      friend class WeakPtr<T>;
public:
      SharedPtr(T* ptr)
           :_ptr(ptr)
           , _pCount(new int())
      {}

      SharedPtr(T* ptr, D d)
           :_ptr(ptr)
           , _d(d)
           , _pCount(new int())
      {}

      SharedPtr(const SharedPtr<T,D>& ap,D d)
           :_ptr(ap._ptr)
           , _pCount(ap._pCount)
           , _d(d)
      {
           ++(*_pCount);
      }

      SharedPtr<T, D>& operator=(const SharedPtr<T, D>& ap)
      {
           if (_ptr != ap._ptr)
           {
                 if (--(*_pCount) == )
                 {
                      _d(_ptr);     //_d為仿函數對象,通過()就可以調用對應的函數
                      delete _pCount;
                 }
                 _ptr = ap._ptr;
                 _pCount = ap._pCount;
                 ++(*_pCount);
           }
           return *this;
      }

      ~SharedPtr()
      {
           if (--(*_pCount) == )
           {
                 if (_ptr)
                 {
                      _d(_ptr);
                      delete _pCount;
                      cout << "~SharedPtr()" << endl;
                 }
           }
      }

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

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

void TestSharedPtr()
{
      DeleteArray<string> da;
      SharedPtr<string,DeleteArray<string>> sp(new string[],da);
      SharedPtr<int> sp1(new int());
      SharedPtr<string, DeleteArray<string>> sp2(new string[]);
      /*SharedPtr<FILE, Fclose>  sp2(fopen("text.txt", "r"), Fclose());
      SharedPtr<FILE, Fclose>  sp3(fopen("text.txt", "w"));*/
}
           

繼續閱讀