【轉】 C++易混知識點4: 本身編寫一個智能指針(Reference Counting)學習auto_ptr和reference counting

這篇文章建大的介紹瞭如何編寫一個智能指針。

介紹: 
什麼是智能指針?答案想必你們都知道,智能指針的目的就是更好的管理好內存和動態分配的資源,智能指針是一個智能的指針,顧名思義,他能夠幫助咱們管理內存。沒必要擔憂內存泄露的問題。實際上,智能指針是一個行爲相似於指針的類,經過這個類咱們來管理動態內存的分配和銷燬。方便客戶端的使用。相比於通常指針,智能指針主要體如今它使用的容易和便捷性。

轉載請註明出處: http://blog.csdn.net/elfprincexu

使用通常指針的問題:

通常狀況下咱們使用指針的問題是什麼?答案是內存管理,簡單來看下面的一個例子:
  1. char* pName  = new char[1024];  
  2. SetName(pName);  
  3. if(null != pName)  
  4. {  
  5.        delete[] pName;   
  6. }  
在上面一段代碼中,咱們會遇到bug呢? 頗有可能在分配內存的時候就出錯了,有可能在被調用的時候指針誤操做了,也有可能在其餘地方操做了。答案太多太多了
咱們仍是從一個實際的例子開始吧,首先看下面的例子:
  1. class Person  
  2. {  
  3.     int age;  
  4.     char* pName;  
  5.     public:  
  6.         Person(): pName(0),age(0){}  
  7.         Person(char* pName, int age): pName(pName), age(age){}  
  8.         ~Person(){}  
  9.   
  10.         void Display()  
  11.         {  
  12.             printf("Name = %s Age = %d \n", pName, age);  
  13.         }  
  14.         void Shout()  
  15.         {  
  16.             printf("Ooooooooooooooooo",);  
  17.         }   
  18. };  
如今,咱們開始使用這個類
  1. void main()  
  2. {  
  3.     Person* pPerson  = new Person("Scott", 25);  
  4.     pPerson->Display();  
  5.     delete pPerson;  
  6. }  
咱們能夠看到,每次咱們新建一個person空間,都要對內存釋放,不然就有可能形成內存泄露。
那咱們能不能想象一下,存在一個相似指針的類來幫助咱們管理內存。
  1. template < typename T > class SP  
  2. {  
  3.     private:  
  4.     T*    pData; // Generic pointer to be stored  
  5.     public:  
  6.     SP(T* pValue) : pData(pValue){}  
  7.     ~SP()  
  8.     {  
  9.         delete pData;  
  10.     }  
  11.   
  12.     T& operator* ()  
  13.     {  
  14.         return *pData;  
  15.     }  
  16.   
  17.     T* operator-> ()  
  18.     {  
  19.         return pData;  
  20.     }  
  21. };  
  22.   
  23. void main()  
  24. {  
  25.     SP<PERSON> p(new Person("Scott", 25));  
  26.     p->Display();  
  27.     // Dont need to delete Person pointer..  
  28. }  
經過泛型編程,咱們可使用任何類型的指針,可是上面還不是完美,考慮一下下面的案例
  1. void main()  
  2. {  
  3.     SP<PERSON> p(new Person("Scott", 25));  
  4.     p->Display();  
  5.     {  
  6.         SP<PERSON> q = p;  
  7.         q->Display();  
  8.         // Destructor of Q will be called here..  
  9.     }  
  10.     p->Display();  
  11. }  
咱們發現,p和q指向同一個實例,因爲SP類沒有定義拷貝函數,系統自動生成一個默認的拷貝函數,實現的是淺賦值,該內存空間被釋放了兩次!
因此,咱們引入Reference Counting的智能指針相當重要,經過對實例被引用的次數來決定該實例是否須要被釋放。
  1. class RC  
  2. {  
  3.     private:  
  4.     int count; // Reference count  
  5.   
  6.     public:  
  7.     void AddRef()  
  8.     {  
  9.         // Increment the reference count  
  10.         count++;  
  11.     }  
  12.   
  13.     int Release()  
  14.     {  
  15.         // Decrement the reference count and  
  16.         // return the reference count.  
  17.         return --count;  
  18.     }  
  19. };  
如今咱們有了一個RC類,這個類只作被引用的次數,惟一的數據成員就是用來跟蹤被引用的次數。
結合咱們剛纔的SP類,咱們稍做改動
  1. template < typename T > class SP  
  2. {  
  3. private:  
  4.     T*    pData;       // pointer  
  5.     RC* reference;     // Reference count  
  6.   
  7. public:  
  8.     SP() : pData(0), reference(0)   
  9.     {  
  10.         // Create a new reference   
  11.         reference = new RC();  
  12.         // Increment the reference count  
  13.         reference->AddRef();  
  14.     }  
  15.   
  16.     SP(T* pValue) : pData(pValue), reference(0)  
  17.     {  
  18.         // Create a new reference   
  19.         reference = new RC();  
  20.         // Increment the reference count  
  21.         reference->AddRef();  
  22.     }  
  23.   
  24.     SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference)  
  25.     {  
  26.         // Copy constructor  
  27.         // Copy the data and reference pointer  
  28.         // and increment the reference count  
  29.         reference->AddRef();  
  30.     }  
  31.   
  32.     ~SP()  
  33.     {  
  34.         // Destructor  
  35.         // Decrement the reference count  
  36.         // if reference become zero delete the data  
  37.         if(reference->Release() == 0)  
  38.         {  
  39.             delete pData;  
  40.             delete reference;  
  41.         }  
  42.     }  
  43.   
  44.     T& operator* ()  
  45.     {  
  46.         return *pData;  
  47.     }  
  48.   
  49.     T* operator-> ()  
  50.     {  
  51.         return pData;  
  52.     }  
  53.       
  54.     SP<T>& operator = (const SP<T>& sp)  
  55.     {  
  56.         // Assignment operator  
  57.         if (this != &sp) // Avoid self assignment  
  58.         {  
  59.             // Decrement the old reference count  
  60.             // if reference become zero delete the old data  
  61.             if(reference->Release() == 0)  
  62.             {  
  63.                 delete pData;  
  64.                 delete reference;  
  65.             }  
  66.   
  67.             // Copy the data and reference pointer  
  68.             // and increment the reference count  
  69.             pData = sp.pData;  
  70.             reference = sp.reference;  
  71.             reference->AddRef();  
  72.         }  
  73.         return *this;  
  74.     }  
  75. };  
接下來咱們看下客戶端調用狀況
  1. void main()  
  2. {  
  3.     SP<PERSON> p(new Person("Scott", 25));  
  4.     p->Display();  
  5.     {  
  6.         SP<PERSON> q = p;  
  7.         q->Display();  
  8.         // Destructor of q will be called here..  
  9.   
  10.         SP<PERSON> r;  
  11.         r = p;  
  12.         r->Display();  
  13.         // Destructor of r will be called here..  
  14.     }  
  15.     p->Display();  
  16.     // Destructor of p will be called here   
  17.     // and person pointer will be deleted  
  18. }  

接下來,咱們着重分析下上面的調用狀況:

1. SP<PERSON> p(new Person("Scott",25));
當咱們建立一個新的智能指針的時候,他的參數類型爲Person, 參數爲一個Person的普通指針, 智能指針p中的狀況是
構造函數被調用,pData 複製新建立的person指針, 同時新建一個RC成員,同時RC調用addReference()函數, reference.count =1 ;
2. SP<PERSON> q = p;
接下來,咱們有定義了一個新的SP智能指針q, 調用SP類的拷貝構造函數,q的pData一樣複製p的pData的值,q的reference拷貝p的reference值
同時,咱們發現,q的reference.count加1, 如今 q的reference.count =2;
3. SP<PERSON> r; r = p;
接下來,咱們建立一個新的空的智能指針r,並調用assigne operator 賦值函數初始化,一樣,因爲r != p, 因此原來的r的空間會被釋放, 而後將p的空間複製給r。
這個時候r的pData一樣指向Person實例的地址,p的reference複製p的reference,而且對reference加1.   如今 r的reference.count =3.

4. 因爲 r,q 生命域到達,rq 的析構函數前後被調用。
r首先被析構, 會對reference.count減一,等於2,發現還沒到0, 因此不會釋放 pdata 和 reference
q其次被析構,會對reference.count減一,等於1,發現還沒到0, 因此不會釋放 pdata 和 reference

5. p的生命域到達,p調用析構函數
p最後被析構,會對reference.count減一,等於0,發現到0, 因此釋放 pdata 和 reference。 此時pdata 就是一開始新建立的Person空間,因此person會被釋放,同時Reference也會被釋放。

總結:整個過程當中,咱們只建立了一次Person實例和Reference實例, 但最多有三個智能指針同時指向他們,經過對實例的被引用次數記錄,來「智能」的判斷何時釋放真正的內存空間。

相關文章
相關標籤/搜索