智慧指標(shared_ptr的實現)
1.實現原理:shared_ptr是利用一個計數器,無論我們使用拷貝建構函式、賦值運算子過載、作為函式返回值、或作為引數傳給一個引數時計數器+1,
當shared_ptr被賦予一個新值或者需要銷燬時,計數器–,直到計數器為0時,呼叫解構函式,釋放物件,並銷燬其記憶體。shaerd_ptr不直接支援管理動態陣列,如果希望使用shared_ptr管理一個動態陣列,必須定製自己的刪除器。
class SharedPtr { public: SharedPtr(T*ptr=NULL) :_ptr(ptr) , _pcount(new int(1)) {} SharedPtr(const SharedPtr&s) :_ptr(s._ptr) , _pcount(s._pcount) { *(_pcount)++; } SharedPtr<T>&operator=(const SharedPtr&s) { if (this!= &s) { if (--(*(this->_pcount)) == 0) { delete this->_ptr; delete this->_pcount; } _ptr = s._ptr; _pcount = s._pcount; *(_pcount)++; } return *this; } T&operator*() { return *(this->_ptr); } T*operator->() { return this->_ptr; } ~SharedPtr() { --(*(this->_pcount)); if (this->_pcount == 0) { delete _ptr; _ptr = NULL; delete _pcount; _pcount = NULL; } } private: T*_ptr; int *_pcount;//指向引用計數的指標 }; int main() { SharedPtr<int>p1(new int(1)); SharedPtr<int>p2(p1); SharedPtr<int> p3 = p2;//呼叫的是拷貝建構函式、因為p3原本是不存在的。 p3 = p2; system("pause"); return 0; }
2.但是其存在一些問題:
問題一:迴圈引用問題:
template<class T> struct ListNode { ListNode(T value) :_value(value) { cout << "ListNode()" << endl; } ~ListNode() { cout << "~ListNode()" << endl; } T _value; shared_ptr<ListNode<T>> _prev; shared_ptr<ListNode<T>> _next; }; void test() { shared_ptr<ListNode<int>> p1(new ListNode<int>(1)); shared_ptr<ListNode<int>> p2(new ListNode<int>(2)); cout << p1.use_count() << endl; cout << p2.use_count() << endl; p1->_next = p2; p2->_prev = p1; cout << p1.use_count() << endl; cout << p2.use_count() << endl; } int main() { test(); system("pause"); return 0; }
當資源要釋放時,p1節點釋放的前提是p2釋放,而p2的釋放又依賴於p1,就形成了一個互相等待的局面,上升到作業系統的話,就等於程序之間形成了死鎖,只不過這裡是資源釋放的依賴關係,而作業系統是資源競爭的關係。最終程式形成了迴圈引用,兩個節點都無法釋放資源,記憶體洩漏也就順理成章。
問題一:迴圈引用問題
解決辦法:所以此時需要利用weak_ptr來解決迴圈引用的問題,weak_ptr它指向的是一個由shared_ptr管理的物件,將一個weak_ptr繫結到一個shared_ptr的物件上,其不會改變shared_ptr的引用計數,一旦最後一個指向物件的shared_ptr被銷燬,物件就會被銷燬,即使有weak_ptr指向物件,物件還是被釋放。
template<class T>
struct ListNode
{
ListNode(T value)
:_value(value)
{
cout << "ListNode()" << endl;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
T _value;
weak_ptr<ListNode<T>> _prev;
weak_ptr<ListNode<T>> _next;
};
void test()
{
shared_ptr<ListNode<int>> p1(new ListNode<int>(1));
shared_ptr<ListNode<int>> p2(new ListNode<int>(2));
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
p1->_next = p2;
p2->_prev = p1;
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
}
int main()
{
test();
system("pause");
return 0;
}
weak_ptr是一個不控制物件生命週期的智慧指標,它指向一個由shared_ptr指向的物件,將一個weak_ptr繫結到由shared_ptr指向的物件上,它不會改變shared_ptr的引用計數,當引用計數等於0時,
物件就會被銷燬,呼叫解構函式,即使weak_ptr指向物件,物件還是會釋放。
問題二:執行緒安全問題
shared_ptr物件提供與內建型別一致的執行緒安全級別,一個shared_ptr指向的物件可以被多個執行緒進行“讀”,一個shared_ptr指向的物件可以被多個執行緒寫入,雖然這些看似是拷貝,但是導致執行緒不安全。(即使這些實 例是拷貝,而且共享下層的引用計數),任何其它的同時訪問的結果會導致未定義行為。總結一下主要有3個方面。
1.同一個shared_ptr被多個執行緒“讀“是安全的。
2.同一個shared_ptr被多個執行緒“寫”是不安全的。
3.共享引用計數不同的shared_ptr被多個執行緒“寫”是安全的。
問題三:記憶體洩漏問題
當我們用malloc申請出來的空間是無法釋放的,因為malloc申請的空間只能用free來釋放,而當我們開啟一個檔案指標,程式執行完畢後,需要關閉檔案,否則會造成記憶體洩漏。
所以要來定製刪除器,定製刪除器還有一個原因是shared_ptr不支援動態陣列管理,若要管理動態陣列,則需自己定製刪除器。