建議慎用boost::weak_ptr來避免智慧指標迴圈引用
阿新 • • 發佈:2018-12-31
為了降低智慧指標迴圈引用的可能性,boost智慧指標引入了weak_ptr(各版本的智慧指標實現基本上都有這個概念)。weak_ptr在構造/析構的時候不會增加/減少引用計數,由於不會增加引用計數,所以與普通智慧指標(有些實現成為strong智慧指標呵呵)相比,它就沒法保證hold住物件,所以當要使用weak_ptr指向物件的時候,必須先將它提升為普通智慧指標,如果這時候物件已經被析構,則提升會失敗報錯。
舉個例子,假設我們要實現一個雙向連結串列。(這裡只是舉個例子。連結串列這種結構效率很重要又很基本,當然應該使用原生指標。)如果結點的prev和next都使用strong智慧指標的話,則引用計數(中括號內的數字)是這樣: head -> Node1[2] <->Node2[2] <->Node3[1]。當連結串列解構函式讓head不再指向Node1後則引用計數變成這樣: Node1[1]<-> Node2[2]<->Node3[1]。發生記憶體洩露了。如果我們規定prev使用weak_ptr。則一開始引用計數是這樣: head-> Node1[1] <…->Node2[1] <…->Node3[1]。這樣當我們讓head不再指向Node1的時候將級聯刪除所有結點。
看起來很美好,但為何我反倒是建議慎用weak_ptr呢?這得回到實際程式設計中為何存在迴圈引用這個問題上來。為何兩個物件要互相引用對方?我發現在絕大多數情況下幾乎總是因為兩個物件存在類似整體/部分的這種關係(要從廣義角度理解整體-部分)。不管是否使用weak_ptr,甚至不管是否使用智慧指標,都需要開發者正確識別出這種關係。使用weak_ptr,一般是讓整體物件持有部分物件的strongptr,部分持有整體的weak ptr,這樣當然是ok。但我更喜歡讓整體物件持有部分物件的strongptr,部分物件則直接持有整體物件的原生指標甚至是引用(一般來說引用更好,要求構造部分物件的時候傳入整體物件作為引數),然後保證整體物件的生命週期涵蓋部分物件的生命週期。這兩種方法相比,後者效率高(原生指標vs物件),程式設計簡單(使用時無需提升+判斷成敗)。當然如果違背了整體物件生命週期涵蓋部分物件生命週期的原則,會死得比較慘。但是我覺得大部分時候這是好事,因為符合Diefast 和 Die fierce原則。使用weak_ptr,一團和氣之下可能掩蓋了錯誤的物件析構順序,而這可能又是由其他邏輯錯誤導致的。
舉個例子,假設我們要實現一個雙向連結串列。(這裡只是舉個例子。連結串列這種結構效率很重要又很基本,當然應該使用原生指標。)如果結點的prev和next都使用strong智慧指標的話,則引用計數(中括號內的數字)是這樣: head -> Node1[2] <->Node2[2] <->Node3[1]。當連結串列解構函式讓head不再指向Node1後則引用計數變成這樣: Node1[1]<-> Node2[2]<->Node3[1]。發生記憶體洩露了。如果我們規定prev使用weak_ptr。則一開始引用計數是這樣: head-> Node1[1] <…->Node2[1] <…->Node3[1]。這樣當我們讓head不再指向Node1的時候將級聯刪除所有結點。
看起來很美好,但為何我反倒是建議慎用weak_ptr呢?這得回到實際程式設計中為何存在迴圈引用這個問題上來。為何兩個物件要互相引用對方?我發現在絕大多數情況下幾乎總是因為兩個物件存在類似整體/部分的這種關係(要從廣義角度理解整體-部分)。不管是否使用weak_ptr,甚至不管是否使用智慧指標,都需要開發者正確識別出這種關係。使用weak_ptr,一般是讓整體物件持有部分物件的strongptr,部分持有整體的weak ptr,這樣當然是ok。但我更喜歡讓整體物件持有部分物件的strongptr,部分物件則直接持有整體物件的原生指標甚至是引用(一般來說引用更好,要求構造部分物件的時候傳入整體物件作為引數),然後保證整體物件的生命週期涵蓋部分物件的生命週期。這兩種方法相比,後者效率高(原生指標vs物件),程式設計簡單(使用時無需提升+判斷成敗)。當然如果違背了整體物件生命週期涵蓋部分物件生命週期的原則,會死得比較慘。但是我覺得大部分時候這是好事,因為符合Diefast 和 Die fierce原則。使用weak_ptr,一團和氣之下可能掩蓋了錯誤的物件析構順序,而這可能又是由其他邏輯錯誤導致的。