不可不表的OSG智能指標之強指標與弱指標

來源:互聯網
上載者:User

    使用OSG的人都知道OSG的記憶體管理方式採用了智能指標,通過智能指標的方式讓OSG自己處理對象的銷毀工作。在OSG中有兩個智能指標類型,一個就是我們再熟悉不過的ref_ptr,另外一個可能很多人不一定經常用,但確實非常好的observer_ptr。這兩種類型都是作為智能指標而出現的,那他們到底有什麼區別?為什麼會出現兩個類似的東西?下面我們慢慢揭開它們的面紗。
    其實它們是兩種智能指標類型,一種是我們熟悉的強指標類型(strong pointer),一種是弱指標類型(weak pointer),ref_ptr就是強指標類型,observer_ptr是弱指標類型,強指標類型ref_ptr和一般意義的智能指標類型相同,通過引用計數來記錄有多少對象在使用該對象,當沒有任何對象使用該對象時該對象自動銷毀,而observer_ptr也指向一個對象,但它沒有引用計數功能,它僅僅記錄該對象的地址,當該對象在其它地方被銷毀後,通過observer_ptr獲得的對象地址將為空白。為什麼稱之為弱指標,本人不知道這個概念的提出者是如何想的,個人感覺可能就是因為它僅僅是對對象地址的一個記錄,不參與對象的生存期管理,所以稱之為弱。不知道您是否這樣認為,反正我這樣認為了,哈哈。
    說到這裡我們應該會有疑問,為什麼還要多個弱智能指標呢?這不成了多個弱智嗎?一個ref_ptr不就夠了嗎?我說說我的看法,不知道是不是準確,僅僅代表個人的觀點,如果我們只用ref_ptr,那就意味著在任何情況下只要我們需要引用某個對象,就要增加它的引用計數,不用了就減少引用計數,我們要明確的第一個問題就是,如果對象數量特別大,頻繁的增加引用計數和減少引用計數本身會降低一定的新能,這一點應該勿容置疑,另外,在很多情況下我們只是需要對對象進行引用以便在物件存留期內方便訪問,而如果使用ref_ptr增加它的引用計數後也就改變了該對象的生存期,對象的生存期需要我們自己來通過額外的手段進行控制,這樣必然增加系統的複雜性,比如我們在系統中建立了若干飛機,同時系統中有一個對所有飛機進行統一管理的飛機管理器,正常的邏輯是當飛機爆炸後我們要銷毀該飛機對象,如果我們在飛機管理器中使用ref_ptr,飛機在別的地方全部解除引用後飛機對象也不會自動銷毀,因為飛機管理器中還存在對其的引用,必須藉助一定判斷手段人為將該飛機對象從飛機管理器解除引用才將飛機對象引用計數降為0進而銷毀該對象。這樣以來我們是不是做了一件出力不討好的事?如果用observer_ptr,我們將不受這種困擾,因為它不干涉對象的生存期,而且不會因為對引用計數的操作降低效率。這就是個人理解的為什麼會出現弱智能指標的原因,可能不準確或不夠全面,歡迎高人修正補充,這裡權當拋磚引玉。
    接下來我們就看看observer_ptr如何?的對對象的引用,如何做到當該對象被銷毀後observer_ptr會知道該情況。其實很簡單,通過名字我們就能猜出來這裡肯定使用了觀察者模式,當對象銷毀後自動通知該觀察者。下面我們通過代碼來看看是不是這樣。
    首先我們要看的一定是該智能指標的建構函式,它有若干建構函式,我們就用最常用也最典型的來看,其它的大同小異而已。
    observer_ptr(T* rp)
    {
        _reference = rp ? rp->getOrCreateObserverSet() : 0;
        _ptr = (_reference.valid() && _reference->getObserverdObject()!=0) ? rp : 0;
    }
    這和ref_ptr一樣通過一個原始對象進行構造,在建構函式裡做了一件事,就是對成員變數的初始化,成員變數一共兩個,一個就是稱之為觀察者集合的對象,另外一個和ref_ptr一樣就是該智能指標引用的原始對象的指標。這裡的重點是那個觀察者集合的成員,它其實就是對對象的一個觀察者,當對象在別的地方被銷毀後,該觀察者會被通知到。
其實上面的rp、_ptr和_reference->getObserverdObject()是一個東西,我們想想,如果不一樣是不是就完蛋了?因為我們要引用和觀察的必須是一個東西!任何一個從Referenced類繼承下來的子類都可以擁有一個觀察者集合,一個觀察者集合觀察一個對象,一個集合可以擁有若干觀察者,這裡不涉及到觀察者集合中的觀察者,只是涉及到被觀察的對象,所以我們為了防止幹擾就不說它了,有興趣的可以看代碼。rp->getOrCreateObserverSet()就是給該智能指標引用的對象建立一個觀察者集合,在建立觀察者集合時,該對象就自然告訴該觀察者集合觀察自己了,不信?代碼為證:
ObserverSet* Referenced::getOrCreateObserverSet() const
{
    ......
    ObserverSet* newObserverSet = new ObserverSet(this);
    ......
}
到這你應該明白剛才說的rp、_ptr和_reference->getObserverdObject()怎麼是一個東西了吧。
好了,我們已經明白observer_ptr是如何觀察其引用的對象了,那對象銷毀的時候它是怎麼知道的呢?什嗎?你不信它能知道?不管你信不信,我反正信了。看代碼:
void Referenced::signalObserversAndDelete(bool signalDelete, bool doDelete) const
{
    ......
    if (observerSet && signalDelete)
    {
        observerSet->signalObjectDeleted(const_cast<Referenced*>(this));
    }
    ......
}

void ObserverSet::signalObjectDeleted(void* ptr)
{
    ......
    _observedObject = 0;
}

看到了吧,當它引用的對象銷毀時會告訴這個觀察者集合:你觀察的對象沒有啦:_observedObject = 0;
到這裡,一去一返就接上了。快完了,別著急,我們再看一行代碼:
inline T* get() const { return (_reference.valid() && _reference->getObserverdObject()!=0) ? _ptr : 0; }
看到什麼了?如果對象銷毀了它返回的是什嗎?空!你答對了,別忘了它不增加引用對象的引用計數,不修改引用的對象的生存期,這就意味著你引用的對象在你使用時可能已經在別的地方銷毀了,所以用的時候一定別忘了判斷一下,要不很容易“嘣!”的一聲。
    完了吧?哦,不行,還有一點,如果我使用的時候,在使用過程中,對象在別的線程被銷毀了怎麼辦?哎呀,確實是啊,這該怎麼辦?豈不是還得用ref_ptr,沒錯,這時候就要用ref_ptr了,否則你正在房頂上揭瓦呢,下面那王八蛋把梯子給你搬走了你摔死了。用ref_ptr!但不是讓你捨棄observer_ptr不用只用ref_ptr哦,看代碼我告訴你用什麼:
    bool lock(ref_ptr<T>& rptr) const
    {
        if (!_reference)
        {
            rptr = 0;
            return false;
        }

        Referenced* obj = _reference->addRefLock();
        if (!obj)
        {
            rptr = 0;
            return false;
        }

        rptr = _ptr;
        obj->unref_nodelete();
        return rptr.valid();
    }
        {
            rptr = 0;
            return false;
        }

        Referenced* obj = _reference->addRefLock();
        if (!obj)
        {
            rptr = 0;
            return false;
        }

        rptr = _ptr;
        obj->unref_nodelete();
        return rptr.valid();
       }
這次你明白如果你意識到會有這種隱患的時候該怎麼做了?我就不翻譯了,看兩個官方注釋:
該函數的注釋是:
 /**
     * Assign the observer_ptr to a ref_ptr. The ref_ptr will be valid if the
     * referenced object hasn't been deleted and has a ref count > 0.
     */

還有對observer_ptr的注釋:
* If you are in any doubt about whether it is safe to access the object safe then use the
  * ref_ptr<> observer_ptr<>.lock() combination. */
全部完畢!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.