c++ - weak_ptr这个智能指针有什么用?
高洛峰
高洛峰 2017-04-17 13:05:59
0
2
545

c++primer:

通过使用weak_ptr,不会影响一个给定的strblob所指向的vector的生存期。但是,可以阻止用户访问一个不再存在的vector的企图。

书上说的作用就这一个,我想不通的是,阻止用户访问一个不再存在的vector,难道就不能通过检测strblob的shared_ptr是否为NULL来实现吗?

高洛峰
高洛峰

拥有18年软件开发和IT教学经验。曾任多家上市公司技术总监、架构师、项目经理、高级软件工程师等职务。 网络人气名人讲师,...

全部回覆(2)
洪涛

補充1:為什麼不能檢測shared_ptr是否為NULL?

因為這就是錯的。 shared_ptrnullptr相等,不代表所引用的物件不存在了。

shared_ptr允許有一個「empty」狀態,代表shared_ptr<T>物件的實例本身存在,但並不指向一個有效的Tshared_ptr重載了===來表示以下意義:

  • shared_ptrnullptr判等,代表判斷是否處在empty狀態;

  • shared_ptr被賦值為nullptr,不代表shared_ptr實例本身沒了,而是把這個shared_ptr實例的狀態改為empty。

  • 一個已經處於empty狀態的shared_ptr仍可以被繼續賦值成為一個有效、有值的shared_ptr

如果有兩個shared_ptr同時指向同一個T在記憶體中的實例,那麼任一個shared_ptrnullptr判等成功,都不能說明指向的物件已經不存在(被銷毀)了!

任何一個具體用例中,必然有多個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的。如同你不能對一個intnullptr一樣。

shared_ptrnullptr由於運算子重載,其本質意義都變了,必須具體分析,而不能想當然!


嗯…非讀者註意:StrBlob只是那本書裡的一個範例類,其內部用shared_ptr維護其管理的vector<string>集合本身的生存期。與C++本身無關。

阻止使用者存取一個不再存在的vector,這個用shared_ptr沒問題,能做到。事實上很多情況下,weak_ptr全改shared_ptr並不會立刻就炸。

但是shared_ptr意味著你的引用和原物件是一個強聯繫。你的引用不解開,原物件就不能銷毀。濫用強聯繫,這在一個運行時間長、規模比較大,或者是資源較為緊缺的系統中,極易造成隱性的內存洩漏,這會成為一個災難性的問題。

更糟的是,濫用強聯繫可能造成循環引用的災難。即:B持有指向A內成員的一個shared_ptrA也持有指向B內成員的一個shared_ptr,此時AB的生命週期互相由對方決定,事實上都無法從記憶體中銷毀。
——這還只是一個簡單的情況。如果存在間接的強引用,或是多於兩個實例之間的強引用,這個相關的bug解起來將是災難性的。

shared_ptr是C++記憶體管理機制的一種放鬆。但放鬆絕不代表可以濫用,否則最後的結局恐怕不會比裸指針到處申請了不釋放更好。

必須明確:從語意上來說,shared_ptr代表了一種對生命週期的自動推論。其本質的意義是:A持有Bshared_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,從而使接受事件分發的對象,在生存週期上可以獲得徹底銷毀的自由,而不和事件分發者產生什麼必然的關聯。

伊谢尔伦

防止循環引用

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板