c++primer:
通过使用weak_ptr,不会影响一个给定的strblob所指向的vector的生存期。但是,可以阻止用户访问一个不再存在的vector的企图。
书上说的作用就这一个,我想不通的是,阻止用户访问一个不再存在的vector,难道就不能通过检测strblob的shared_ptr是否为NULL来实现吗?
PHP中文网2017-04-17 13:07:59
補充1:為什麼不能檢測shared_ptr
是否為NULL?
因為這就是錯的。 shared_ptr
和nullptr
相等,不代表所引用的物件不存在了。
shared_ptr
允許有一個「empty」狀態,代表shared_ptr<T>
物件的實例本身存在,但並不指向一個有效的T
。 shared_ptr
重載了==
和=
來表示以下意義:
shared_ptr
和nullptr
判等,代表判斷是否處在empty狀態;
shared_ptr
被賦值為nullptr
,不代表shared_ptr
實例本身沒了,而是把這個shared_ptr
實例的狀態改為empty。
一個已經處於empty狀態的shared_ptr
仍可以被繼續賦值成為一個有效、有值的shared_ptr
。
如果有兩個shared_ptr
同時指向同一個T
在記憶體中的實例,那麼任一個shared_ptr
和nullptr
判等成功,都不能說明指向的物件已經不存在(被銷毀)了!
任何一個具體用例中,必然有多個shared_ptr
指向同一個實例(如果沒有多個你shared幹嘛)。所以判空某 1 個shared_ptr
去確定被指向的對像是否已被銷毀,這個在語意和實效上都絕無任何正確(哪怕是「蒙對了」)的可能!
補充2:shared_ptr
到底是什麼?
首先shared_ptr
雖然名為“智慧指針”,但其實他不是指針。一個shared_ptr<T>
定義出來的東西,只是一個單純的變量,儲存了一個實例化出來的class
。其本質上等同於以下程式碼:
struct MyClass
{
public:
int MyValue;
MyClass(int my_value) {
MyValue = my_value;
}
}
void main()
{
auto a = new MyClass(1);
// a 是一个单纯的变量,不是指针
// a 天然与 main() 函数拥有等同的生命周期
}
為什麼shared_ptr
能夠像指標一樣使用*
、&
、->
等運算符,是因為shared_ptr
類別把這些運算子重載了,從而讓程式編寫者「看起來」像是在用指針而已。
一個非指標的局部變量,如果不靠運算子重載,你是不能判nullptr
的。如同你不能對一個int
判nullptr
一樣。
shared_ptr
判nullptr
由於運算子重載,其本質意義都變了,必須具體分析,而不能想當然!
嗯…非讀者註意:StrBlob
只是那本書裡的一個範例類,其內部用shared_ptr
維護其管理的vector<string>
集合本身的生存期。與C++本身無關。
阻止使用者存取一個不再存在的vector
,這個用shared_ptr
沒問題,能做到。事實上很多情況下,weak_ptr
全改shared_ptr
並不會立刻就炸。
但是shared_ptr
意味著你的引用和原物件是一個強聯繫。你的引用不解開,原物件就不能銷毀。濫用強聯繫,這在一個運行時間長、規模比較大,或者是資源較為緊缺的系統中,極易造成隱性的內存洩漏,這會成為一個災難性的問題。
更糟的是,濫用強聯繫可能造成循環引用的災難。即:B
持有指向A
內成員的一個shared_ptr
,A
也持有指向B
內成員的一個shared_ptr
,此時A
和B
的生命週期互相由對方決定,事實上都無法從記憶體中銷毀。
——這還只是一個簡單的情況。如果存在間接的強引用,或是多於兩個實例之間的強引用,這個相關的bug解起來將是災難性的。
shared_ptr
是C++記憶體管理機制的一種放鬆。但放鬆絕不代表可以濫用,否則最後的結局恐怕不會比裸指針到處申請了不釋放更好。
必須明確:從語意上來說,shared_ptr
代表了一種對生命週期的自動推論。其本質的意義是:A
持有B
的shared_ptr
,代表B
的生命週期反而完全覆蓋了A
。以樹狀結構的層級來理解,指標持有者是下級,指針指向的目標反而是上級-下級短命,上級長存;上級不存,下級焉附。
從這個意義上,有些關係你用shared_ptr
就表示不了了:
隸屬關係中,祖先到子孫的關係。例如一個物件容器,具體物件找其隸屬的容器可以用shared_ptr
,但是容器去找具體的物件則不行,因為容器不能要求物件生存的比自己更久。
生命週期沒有本質關聯的兩個無關對象。例如一個全域事件管理器(訂閱者模型),事件訂閱者建構時把自己註冊進來,析構時把自己解註冊掉。管理器要維護到達這個物件的指標(從而發送訊息),但絕對不允許染指物件的生命週期,物件的析構需要無視訂閱者的存在,只由其他業務所必須的強引用來控制。
這種時候就是weak_ptr
的用處。 weak_ptr
提供一個(1)能夠確定對方生存與否(2)互相之間生命週期無幹擾(3)可以臨時借用一個強引用(在你需要引用對方的短時間內保證對方存活)的智能指針。
而weak_ptr
要求程式設計師在運作時確定生存並加鎖,這也是邏輯上必須的本徵複雜度-如果別人活的比你短,你當然要:(1)先確定別人的死活(2)如果還活著,就給他續命續到你用完了為止。
事實上弱引用和強引用這個概念,在有GC的語言中也是一個需要注意的問題。以C#為例:
public class EventDisposer
{
private Dictionary<int, WeakReference<IEventListener>> pendingEvents;
private void Dispose(int event_id)
{
var reference = pendingEvents[event_id];
IEventListener context;
if (reference.TryGetTarget(out context))
{
// context is valid from here to the end of function
context.Trigger();
}
else
{
// context has already been destroyed...
}
}
}
這個程式使用WeakReference<IEventListener>
替代強引用的IEventListener
,從而使接受事件分發的對象,在生存週期上可以獲得徹底銷毀的自由,而不和事件分發者產生什麼必然的關聯。