[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)。
實現簡單的智慧指標:
4.1 unique_ptrclass 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; };
當我們定義一個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成員變數指向對方的問題,從而不會引起引用迴圈。