1. 程式人生 > >[C++] 智慧指標與迴圈引用

[C++] 智慧指標與迴圈引用

   1、智慧指標的出現

      在我們寫程式的時候總會遇到一些需要new的問題,在沒有智慧指標的時候,我們只能收到的去delete,一旦我們忘記就會出現記憶體洩漏的問題。智慧指標的出現就是為了解決這一問題,讓我們new的物件,能夠在使用完畢之後自己delete,而不用我們手動delete。

   2、智慧指標的歷史

        1、auto_ptr(C++98)

2、unique_ptr(C++11)

3、shared_ptr(C++11)

4、weak_ptr(C++11)

   auto_ptr在C++98中提出在C++11中已經被拋棄,就不再敘述。

   3、智慧指標實現原理

     智慧指標是一種資源管理類,通過對原始指標進行封裝,在資源管理物件進行析構時對指標指向的記憶體進行釋放;通常使用引用計數方式進行管理。在指標進行拷貝的時候計數器會加1,而對一個物件進行賦值時,賦值操作符減少左運算元所指物件的引用計數(減1,如果引用計數為減至0,則刪除物件),並增加右運算元所指物件的引用計數(加1)。

  實現簡單的智慧指標:

class Counter
{
 friend class SmartPointer;
public:
 Counter()
 {
  ptr = NULL;
  cnt = 0;
 }
 Counter(Object* p)
 {
  ptr = p;
  cnt = 1;
 }
 ~Counter()
 {
  delete ptr;
 }
private:
 Object* ptr;
 int cnt;
};

class SmartPointer
{
public:
 SmartPointer(Object* p)
 {
  ptr_counter = new Counter(p);
 }
 SmartPointer(const SmartPointer &sp)
 {
  ptr_counter = sp.ptr_counter;
  ++ptr_count->cnt;
 }
 SmartPointer& operator=(const SmartPointer &sp)
 {
  ++sp.ptr_counter->cnt;
  --ptr_counter->cnt;
  if (ptr_counter->cnt == 0)
  {
   delete ptr_counter;
  }
  ptr_counter = sp.ptr_counter;
 }
 ~SmartPointer()
 {
  - -ptr_counter->cnt;
  if (ptr_counter->cnt == 0)
  {
   delete ptr_counter;
  }
 }
private:
 Counter* ptr_counter;

}; 
     4.1 unique_ptr

      當我們定義一個unique_ptr的時候,需要將其繫結到一個new返回的指標。

      只能有一個uniqu_ptr指向物件,也就是說它不能被拷貝,也不支援賦值,但是我們可以通過move來移動。

unique_ptr<string> p3 (new string ("test");  
unique_ptr<string> p4;                      
p4 = p3;    
    上述程式碼是錯誤的。但是如說使用auto_ptr就是沒有問題的
auto_ptr<string> p1(new string ("test") ;
auto_ptr<string> p2;                       
p2 = p1;    

  當我們使用auto_ptr時,可以有效的防止p2,p1指向同一物件,但是因為p2現在指向字串,剝奪了了p1的權利,如此p1就不會指向任何物件,但是如果我們再使用p1的話,就會出現未知錯誤!使用unipue_ptr就可以很好的避免這一點,並且C++有一個標準庫函式std::move(),讓你能夠將一個unique_ptr賦給另一個,使用move後,原來的指標仍轉讓所有權變成空指標,可以對其重新賦值。
unique_ptr<string> ps1, ps2;
ps1 = new string("test");
ps2 = move(ps1);
ps1 = new string("ok");
cout << *ps2 << *ps1 << endl;
  而且,程式試圖將一個 unique_ptr 賦值給另一個時,如果源 unique_ptr 是個臨時右值,編譯器允許這麼做;如果源 unique_ptr 將存在一段時間,編譯器將禁止這麼做
  
unique_ptr<string> test(const char * str)
{
    unique_ptr<string> temp (new string (str)); 
    return temp;
}
unique_ptr<string> ps;
ps = demo('test");

    4.2shared_ptr

 採用引用計數的智慧指標。 shared_ptr基於“引用計數”模型實現,多個shared_ptr可指向同一個動態物件,並維護了一個共享的引用計數器,記錄了引用同一物件的shared_ptr例項的數量。當最後一個指向動態物件的shared_ptr銷燬時,會自動銷燬其所指物件(通過delete操作符)。shared_ptr的預設能力是管理動態記憶體,但支援自定義的Deleter以實現個性化的資源釋放動作.

  4.3weak_ptr

  weak_ptr 提供對一個或多個 shared_ptr 例項所屬物件的訪問,但是,不參與引用計數。

    5、迴圈引用

    迴圈引用”簡單來說就是:兩個物件互相使用一個shared_ptr成員變數指向對方的會造成迴圈引用。

    即A內部有指向B,B內部有指向A,這樣對於A,B必定是在A析構後B才析構,對於B,A必定是在B析構後才析構A,這就是迴圈引用問題,違反常規,導致記憶體洩露。

   解決迴圈引用方法:

   1. 當只剩下最後一個引用的時候需要手動打破迴圈引用釋放物件。
   2. 當A的生存期超過B的生存期的時候,B改為使用一個普通指標指向A。
   3. 使用weak_ptr打破這種迴圈引用,因為weak_ptr不會修改計數器的大小,所以就不會產生兩個物件互相使用一個shared_ptr成員變數指向對方的問題,從而不會引起引用迴圈。