一、shared_ptr的循環引用問題
在上篇文章中介紹了智能指針的基本知識,我們感覺shared_ptr好像特別厲害的樣子,其
實它就是特別厲害,只是它有循環引用的問題,看下邊的代碼:
struct Node { int _data; shared_ptr_next; shared_ptr _prev; }; void test() { shared_ptr sp1(new Node); shared_ptr sp2(new Node); cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; sp1->_next = sp2; sp2->_prev = sp1; cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; }
上邊的程序最終會輸出1 1 2 2(數字之間以回車間隔),程序結束時,sp1和 sp2並不
會被析構(引用計數未被變為0)。下邊圖解。
注意:上邊畫綠線部分提到sp1和sp2都被釋放,並不是空間真正被釋放,而是相應空間
的引用計數減1.
那麼,該如何解決這個問題呢?
我們引入了weak_ptr弱指針,它是用來輔助shared_ptr,並不可以單獨使用。將一
個weak_ptr綁定到一個shared_ptr,並不會改變shared_ptr的引用計數,一旦最後一個
指向對象的shared_ptr被銷毀,對象就會被銷毀,即使有weak_ptr指向對象,對象還是
會被釋放。所以,上邊定義的Node結構體改成下邊這樣,就能解決循環引用的問題。
struct Node { int _data; weak_ptr_next; weak_ptr _prev; };
二、定制刪除器
智能指針的引入是為了解決因程序執行流的改變而引起的內存洩漏的問題。在構造函數
中開辟和初始化,在析構函數中完成清理工作。智能指針是模板,所以就應該可以解決
各種類型的內存洩漏問題。在構造函數中打開的文件,在析構函數中完成文件的關閉,
在構造函數中malloc開辟的空間,在析構函數中用free釋放。所以,我們必須為每一種
類型寫出對應的內存釋放的函數。
通過仿函數(重載())來實現。下邊展示定制刪除器的模擬實現:
#includeusing namespace std; #include template class SharedPtr { public: SharedPtr(T* ptr) :_ptr(ptr) ,_pCount(new int (1)) ,_del(D()) {} ~SharedPtr() { if (--*_pCount == 0) { //delete _ptr; _del(_ptr); delete _pCount; } } SharedPtr(const SharedPtr & sp) { _ptr = sp._ptr; _pCount = sp._pCount; ++*_pCount; } SharedPtr & operator=(const SharedPtr & sp) { if (_ptr != sp._ptr) { if (_ptr != nullptr) { if (--*_pCount == 0) { //delete _ptr; _del(_ptr); delete _pCount; } } else { _ptr = sp._ptr; _pCount = sp._pCount; ++(*_pCount); } } return *this; } private: T* _ptr; int* _pCount; D _del; }; //默認刪除器 struct DefaultDelete { void operator() (void* ptr) { delete ptr; } }; struct Fclose { void operator() (void* ptr) { fclose((FILE* )ptr); } }; struct Free { void operator() (void* ptr) { free(ptr); } }; void test_del() { SharedPtr sp1(new int (1)); SharedPtr sp2(fopen("hello.txt","w")); }
當智能指針管理的是文件時,出作用域時就會調用關閉文件的函數。這就是所謂的定制
刪除器。