智能指針的研究

    由於C++中堆內存是由程序員自己管理,要把對象分配在堆中,必須手動調用new,並且必須記住調用delete釋放之前分配的內存。大量的new和delete必定會造成代碼十分混亂,並且很容易出現懸空指針和內存泄漏等問題,smart pointer智能指針就是幫助程序員更好地處理這些問題。
    研究了3個智能指針實現的框架(boost,android,還有一個是內部庫),每個框架的實現大體思路都一致,但在一些個別地方有細微的差別,當然,這3個智能指針的實現都針對自身不同的需求做了優化處理。

總體實現思路
    首先智能指針的總體思路就是利用引用計數來判斷對象是否仍然在使用,當引用計數爲0,則刪除該對象。但智能指針有一個很大的問題,就是循環引用導致兩者都無法釋放,造成內存泄漏,因此就需要引入另外一個引用計數來解除這種這個循環,這就是弱指針,上面那個決定對象生命週期的指針也叫做強指針。
    智能指針的實現,可以分爲三個功能模塊。
    首先,第一個就是引用計數模塊,這個模塊的實現通常需要整形數值來記錄當前對象被引用的次數,根據上面的討論,強/弱指針總共需要兩個整形數值,另外考慮到多線程訪問問題,需要使用原子增減,另外這個整形數值的存放地方也是可以值得思考,例如可以對於指向同一個對象的智能指針內部都存放一個引用,都指向同一個引用變量;又或者可以存放在對象的類內部,並提供方法去改變引用變量的值。
    第二個就是,指針的操作符重載問題。這個其實也是實現一個擁有指針行爲的自定義類型的問題。當然,由於我們有強/弱指針,因此需要考慮到兩種指針之間的賦值問題。
    第三個,就是內存分配問題,簡單來說,就是可替換的內存分配機制。一般來說都是利用new/delete直接向系統分配堆上的空間來保留對象,但如果想減少內存碎片,提高分配效率,可以考慮使用內存池來替代直接分配。
    除此之外,也需要有一些要注意的地方,比如說這個智能指針是否數組類型,如果支持,則注意在delete的時候,對於數組類型要注意使用delete[]操作符。另外也要弱指針轉換成強指針問題,如強指針已經全部釋放,當前對象事實上已經無效等情況。

框架具體實現
    對於以上設計需求,三個不同的實現框架給出了不同的答案。
1、引用計數模塊。
    boost的實現是把引用計數保留在指針內部,每個指針都指向同一個引用計數;而android的sp/wp,以及內部庫實現則是把引用計數保留在對象的內部。大致代碼如下:

//boost:
template<class T>
class shared_ptr
{
    //...


private:
    shared_count pn;
}


class shared_count
{
    //...
private:
    //sp_count_base內部
    sp_count_base *pi;
}


class sp_counted_base
{
private:
    long use_count_;
    long weak_count_;
}

    可以看到boost內部的share_count類就是引用計數模塊,內部的sp_count_base就是具體的實現,指向同一對象的shared_ptr保證內部的sp_count_base指向同一個引用計數對象。weak_ptr的實現與shared_ptr類似,使用的是weak_count類,內部同樣保留一個sp_count_base的指針,此處忽略。
    
    再來看看android和內部庫的實現。


//android:
class RefBase
{
private:
    weakref_type* mrefs
}


class weakref_impl : public weakref_type
{
public:
    volatile int32_t mStrong;
    volatile int32_t mWeak;
}

    需要把具體對象繼承自RefBase後,該對象纔可賦值給智能指針。可以看到RefBase內部保留的weakref_type就是事實上的引用計數模塊。

    事實上,這三種不同的實現裏,還可以看到一些不同之處。例如,boost的這種實現,使得智能指針支持數組,但android和內部庫實現則不支持(由於兩種實現都需要具體對象繼承自某個類,這就限制了數組、基本類型等不可能使指針模版實例化)。由於boost要支持數組,因此其單獨添加了對數組類型偏特化,調用delete[]操作符釋放對象。boost的實現了對於所有類型都使用智能指針的需求,另外,事實上boost內部在引用計數的具體實現模塊(上面被省略的繼承自sp_count_base的sp_counted_impl_**的類),重載new/delete操作符,可以轉換爲使用boost內部的quick_allocator來分配引用計數模塊。
    另外,內部庫和android的實現也有另外的不同之處。android要求類必須要顯式繼承自RefBase,並且需要顯式調用new分配對象。對於內部庫,則把這個實現細節給隱藏起來,並且構造對象的細節也放到了內部,這樣就可以方便替換new/delete分配釋放對象。唯一的不足之處恐怕就是直接在必須要在棧上構造CComOBj對象,這樣對於先前使用了new/delete分配對象的程序來說,要移植該庫來說可能會增加一些麻煩。
    指向同一對象的強指針的生命週期都一定會比指向對象要長,這是由於所有強指針被釋放的時候,被指向的對象纔會被析構。但對於弱指針就不同,弱指針的生命週期可能會比指向對象要長,由於弱指針主要是爲了破除強指針的引用循環,對象的生命週期是strong_count(強引用計數)決定的,引用計數的生命週期則是由所有的智能指針決定的,只有當最後一個智能指針析構的時候,纔可以刪除引用計數模塊。爲了能夠維護好對象以及引用計數模塊的生命週期,可以這樣做,首先,弱指針可能比對象生命週期要長,爲了弱指針能夠正確使用,一定要分配在堆上。另外,要保證strong_count<=weak_count,這樣就可以當weak_count(弱引用計數)爲0的時候,刪除該引用計數模塊。

2、操作符重載。
    事實上三者實現都是大致相同,重載*,->,=操作符,並且注意調用addStrongRef,decStrongRef,addWeakRef,decWeakRef(對於三種實現,這裏暫時同一表達爲增加/釋放強引用計數,增加/釋放弱引用計數)的適時調用。
    另外,這裏還要考慮弱指針的職責。boost庫把弱指針定義一種專門輔助強指針打破引用循環的指針功能類,因此,boost庫的弱指針沒有重載任何有關raw指針的操作符,並且只能夠經過lock操作提升至強指針纔可以訪問弱指針的對象。android實現的弱指針在此基礎上,也會對象的生命週期進行管理,也就是說,如果分配出來的對象,只有弱指針引用過,當所有弱指針被析構的時候,該對象就同時會被析構(boost庫不會同時也不可能出現,因爲沒有重載對應操作符)。
    對於addStrongRef,decStrongRef,addWeakRef,decWeakRef的實現。由於內部的引用計數模塊都是智能指針內部new分配在堆上,因此必須要手動釋放。由於三種實現都有兩個不同的計數strong_count,weak_count,分配對應強指針和弱指針的引用計數。這時要考慮可能出現只有強指針,或者只有弱指針,或者同時存在強、弱指針指向一個對象的情況。
    其實,必須要保證的是strong_count<=weak_count,當strong_count=0的時候要刪除對象,weak_count=0的時候刪除引用計數模塊。
    對於boost,首先引用計數模塊只能在強指針內部進行初始化,同時strong_count和weak_count的值爲1。然後addStrongRef的時候只增加strong_count的值,decStrongRef的時候減去strong_count的值,並且判斷,如果剛好strong_count爲0的時候,同時調用decWeakRef,decWeakRef會當weak_count爲0的時候,刪除引用計數模塊。由於weak_count初始化值爲1,因此始終保證了引用模塊的有效性。
    android由於允許弱指針只有弱指針指向對象,因此有些特殊。只有弱指針的情況下,當所有弱指針都被釋放的時候,同時會釋放指向的對象(在代碼裏貌似提供了方法來改變這一行爲,即不釋放對象,但發現已經被移除)。另外android在addStrongRef,decStrongRef的時候都會同時在內部分別調用addWeakRef,decWeakRef,這樣就保持weak_count>=strong_count,這樣就可以判斷當真正當weak_count == 0的時候釋放掉引用計數模塊。

3、內存分配問題。
    boost庫對於引用計數模塊分配提供了一個額外的實現類來管理分配和釋放(sp_counted_impl_xx繼承自sp_counted_base)。sp_counted_impl_xx主要解決了一些內存分配和回收的問題。sp_counted_impl_xx通過重載new和delete操作符來改變對象分配和回收行爲。sp_counted_impl_xx允許通過定義宏來在new和delete操作符裏使用內部的quick_allocator來改變分配引用計數模塊的行爲,quick_allocator通過簡單的預分配,內存對齊,還有鏈表保留回收內存重複利用等機制進行內存管理。另外delete操作符還會根據是否數組調用對應的delete析構語法。
    android沒有提供這個內存分配機制。但提供了另外一些跟蹤指針分配和釋放的代碼。由於引用計數模塊在RefBase類內部,因此可以在addStrongRef,decStrongRef,addWeakRef,decWeakRef等方法對當前引用對象以及智能指針進行跟蹤。這樣能夠隨時動態打印之前所有的分配和回收信息方便對象分配狀態進行診斷。另外,android爲了能夠延遲初始化時機或者希望在析構之前執行一些清理操作,因此提供了onFirstRef、onLastStrongRef、onLastWeakRef等函數重載。

一個簡單的實現
    針對一般的應用情況,設計出一個簡單的實現。首先爲了簡化設計,智能指針不支持數組,因此引用計數模塊保存在對象派生類裏面,另外可以在派生類內部添加內存管理、分配回收追蹤等機制。然後弱指針不直接與對象交互,只能通過lock操作轉換成強指針纔可以訪問對象指針。
    具體設計如下:
template<typename T>
class RefWrppaer 
{
    //增加類數量,但能夠方便使用並且能夠支持第三方類擴展
    template<typename Base>
    class RefBase : public Base
    {
    public:
        //以下這4個方法內部可以通過id(wp/sp的指針值)與自己的指針關聯起來
        //方便進行跟蹤
        void addStrongRef(const void *id);
        void decStrongRef(const void *id);

    
        void addWeakRef(const void *id);
        void decWeakRef(const void *id);

        //可選
        //重載new/delete,提供內存管理
        void * operator new( std::size_t );
        void operator delete( void * p );
    private:
        int strong_count;   //初始化爲1
        int weak_count;     //初始化爲1
    }


    //提供多參數版本
    T* wrap(T* base)
    {
        return new RefBase<T>;
    }
}


template<typename T>
clas sp
{
public:
    sp() : m_ptr(0) { }
    sp(T* other);
    sp(const sp<T>& other);
    template<typename U> sp(U* other);
    template<typename U> sp(const sp<U>& other);

    inline  T&      operator* () const  { return *m_ptr; }
    inline  T*      operator-> () const { return m_ptr;  }
    inline  T*      get() const         { return m_ptr; }


    sp& operator = (T* other);
    sp& operator = (const sp<T>& other);

    template<typename U> sp& operator = (const sp<U>& other);
    template<typename U> sp& operator = (U* other);


    //operator == / != / > / < / >= / <=


private:
    T* m_ptr;
}


templeate<typename T>
class wp
{
    //wp沒有任何與raw pointer接觸的操作符
public:
    wp() : m_ptr(0) { }
    wp(T* other);
    wp(const wp<T>& other);
    wp(const sp<T>& other);
    template<typename U> wp(U* other);
    template<typename U> wp(const sp<U>& other);
    template<typename U> wp(const wp<U>& other);


    wp& operator = (const sp<T>& other);
    wp& operator = (const wp<T>& other);


    template<typename U> wp& operator = (const sp<U>& other);
    template<typename U> wp& operator = (const wp<U>& other);


    //唯一可以訪問對象的方法。
    //不一定保證返回對象有效
    sp<T> lock() const;


    //operator == / != / > / < / >= / <=


private:
    T* m_ptr;
}





    其中RefWrppaer屬於一個工廠類,主要負責構造和派生類對象,雖然會增加類對象數量,但這樣就可以方便支持第三方類擴展。


具體參考:
android的智能指針實現:
4.3代碼目錄:
frameworks/native/include/utils/RefBase.h
frameworks/native/libs/utils/RefBase.cpp
frameworks/native/include/utils/StrongPointer.h


boost實現目錄:
smart_ptr/weak_ptr.hpp
smart_ptr/detail/shared_count.hpp
smart_ptr/detail/sp_counted_impl.hpp
smart_ptr/detail/sp_counted_base_w32.hpp
smart_ptr/shared_ptr.hpp













發佈了36 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章