本文預設讀者是已經了解了觀察者模式智慧型指標的來龍去脈,智慧型指標的介紹可以參考我的部落格:話說智慧型指標發展之路。
之前看到迴圈引用的時候,總是只能看到乙個很簡單的例子,比如這個c++ 智慧型指標及 迴圈引用問題,我覺得挺沒意思,因為沒有實際的意義,就想找個好點的例子來分享。
所以,這篇文章將專注於展示乙個更實際點的例子來說明實際工作中確實會碰到shared_ptr的迴圈引用的問題,然後再展示如何使用weak_ptr來解決。
下文即將展示的**,編譯不需要boost庫,只需要編譯器支援c++11即可!比如我使用的g++就可以這樣來編譯:
g++ -std=c++11 example.cpp -o out
建議先看上面說到的那個簡單的例子,然後再來看下面這個例子。迴圈引用形成的原因概括起來就是「你中有我,我中有你」。
建議看**的順序為:main函式是怎麼呼叫各個介面的,然後再看類的宣告,接著再看介面的實現,最後結合輸出,分析為什麼!
#include
#include
#include
#include
using
namespace
std;
// 前向宣告,以便observer能夠使用這個型別
class observable;
// 觀察者類
class observer ;
// 被觀察的物體類
class observable ;
int main()
cout
<< "\nget out now...\n"
<< endl;
p->notify();
cout
<< "observable's use_count is: "
<< p.use_count() << endl;
return0;}
// observable的介面實現
void observable::register_(shared_ptr
ob)
void observable::notify()
}observable::~observable()
// observer的介面實現
observer::observer(const
char* str)
: m_info(str) {}
void observer::update()
void observer::observe(shared_ptr
so, shared_ptr
me)
observer::~observer()
執行輸出為:
notify, use_count = 2下面用幾個自問自答的問題來分析這個輸出:update: hello
notify, use_count = 2
update: hi
notify, use_count = 2
update: how are you
get out now…
notify, use_count = 1
update: hello
notify, use_count = 1
update: hi
notify, use_count = 1
update: how are you
observable』s use_count is: 4
因為乙份引用在main函式裡,乙份存在observable物件的vector成員裡。
因為main函式裡的那份引用一旦出了區域性的作用域,就析構了,所以shared_ptr的引用計數減少了1,而存在observable物件裡的那乙份沒有析構,所以計數為1。
因為main函式裡有乙個引用(shared_ptr的物件p),而o1,o2,o3這三個物件裡分別有乙個對同個物件的引用,所以總數是4。
因為,observable的引用計數是4,即使在離開main函式之前會析構掉其作用域內的shared_ptr
物件,計數值也只減少為3,所以它不會析構。
而所有observer物件的引用計數由於observable物件沒有析構,所以一直保留著乙份在其中,引用計數為1,也不會析構。
這樣就死鎖了嘛,a等b析構,b等a析構。所以誰都沒成功析構掉,這就造成了記憶體洩露!!!至於記憶體洩露的危害,煩請自行檢索資料。
weak_ptr和shared_ptr搭配就可以完美解決迴圈引用的問題!!!
怎麼做到的呢?
weak_ptr是一種弱引用,被它引用的資源,計數值不會因為它而改變。
同上,先看示例**,再來結合輸出結果分析:
#include
#include
#include
#include
using
namespace
std;
// 前向宣告,以便observer能夠使用這個型別
class observable;
// 觀察者類
class observer ;
// 被觀察的物體類
class observable ;
int main()
cout
<< "\nget out now...\n"
<< endl;
p->notify();
cout
<< "observable's use_count is: "
<< p.use_count() << endl;
return0;}
// observable的介面實現
void observable::register_(weak_ptrob)
void observable::notify() else
}}// observer的介面實現
observer::observer(const
char* str)
: m_info(str) {}
void observer::update()
void observer::observe(shared_ptr
so, shared_ptr
me)
observer::~observer()
輸出結果為:
notify, use_count = 1還是幾個自問自答的問題:update: hello
notify, use_count = 1
update: hi
notify, use_count = 1
update: how are you
observable』s use_count is: 4
~observer(): how are you
~observer(): hi
~observer(): hello
get out now…
notify, use_count = 0
erase when notify…
notify, use_count = 0
erase when notify…
notify, use_count = 0
erase when notify…
observable』s use_count is: 1
~observable()…
因為乙份引用在main函式裡,而observable物件裡存放的是observer物件的weak_ptr物件,不參與計數,所以計數值只是1。
因為main函式裡的那份引用一旦出了區域性的作用域,就析構了,所以shared_ptr的引用計數減少了1,而存在observable物件裡的那乙份是弱引用,沒有參與計數,所以計數值降低為0(從輸出也可以看出來,第二次notify時,use_count變為0了),自然就析構了。
因為weak_ptr物件們所引用的資源都已經析構了,所以當它提公升為shared_ptr後,程式判斷該物件為空,表示資源已經失效了,自然就輸出了erase when notify...
。
因為,在離開區域性作用域之前,observable的引用計數是4;而離開區域性作用域時,三個observer物件都析構了,所以observable的引用計數減少為1。
然後最後程式結束,即將退出main函式之前,會析構掉其作用域內的observable物件,所以其計數值減少為0,自然就真正析構了。
綜上,至此,很簡潔地使用weak_ptr和shared_ptr搭配,避免了互相引用的雙方都使用shared_ptr而造成的迴圈引用問題!
cplusplus.com的api說明對學習智慧型指標很有幫助,因為你能快速全面了解其提供的介面,shared_ptr,weak_ptr
boost的shared ptr迴圈引用
boost的智慧型指標給程式設計帶來了極大的便利,不需要關心記憶體的釋放,不要要呼叫delete,而且還可以定製delete的方法。其實boost的智慧型指標是可以當成scope exit來用的,同樣是退出時處理。但是凡事都是有利有弊,boost的shared ptr如果在迴圈引用的時候會出現無法釋...
shared ptr 定製刪除器 和 迴圈引用
前面我們介紹智慧型指標的時候,說了兩個智慧型指標分別是 auto ptr和scoped ptr,auto ptr有問題,為此,scoped ptr的出現解決了這個問題,scoped ptr太霸道,不允許別人和他共用一塊記憶體空間,所以,我們還得想辦法解決這個問題。回想我們以前看過內容,當提到共用一塊...
迴圈引用的處理
迴圈引用 兩個類互相引用,導致單純互相引用標頭檔案,編譯無法通過。解決方案 首先,要理解宣告和實現之間的差別。宣告只需要在使用類的前面新增class 而實現是包含具體成員函式和變數的類。如例1.class a class b 1.此時可在a的前面宣告class b,就可以使用b了。在b中只需要正常引...