Android基礎--智能指針

智能指針分為3類為輕量級指針(Light Pointer)、強指針(Strong Pointer)和弱指針(Weak Pointer)。輕量級指針采用的是簡單的計數,可以認為是強指針的簡化版本。在播放器的C++代碼中,特別是涉及到binder通訊的地方有很多智能指針的應用,比如jni中

sp<MediaPlayer> mp = getMediaPlayer(env, thiz);

sp是強指針的意思。

基于Android8.1 代碼地址
system/core/include/utils/RefBase.h
system/core/include/utils/StrongPointer.h
system/core/include/libutils/RefBase.cpp

輕量級指針

指針的問題

C/C++指針問題可以歸納為以下2類:

  1. 野指針
  • 指針未初始化,當我們去判斷一個指針是否可用時,往往會判斷指針是否為NULL。未初始化的指針,它有可能指向了一個未知的地址。指針初始化是必須要養成的習慣。
  • 將對象delete后,未將指向它的指針設為NULL,這種情況同指針未初始化一樣。
  • 另外一種情況是有多個指針指向了對象A,當某個地方將對象A delete后,操作地方地方的指針,就是對一個非法的內存進行操作
  1. new了對象后沒有delete
    動態分配內存是需要程序員主動去刪除的,不然會造成內存泄漏。比如在一個函數中new了一個對象,并將這個對象作為返回值返回。對于一個多人維護的比較復雜工程,如果有這樣的函數,并不一定所以人都會留意去釋放內存,或者改對象需要被多個地方使用到,要在合適的地方去釋放該對象不是那么好處理的。

解決指針問題

智能指針就是為了解決以上問題的,在了解Android 智能指針之前。先來分析下如何解決以上問題,
首先需要有一種能夠自動釋放的方法,而對于程序代碼而言,只有棧內存才會自動去釋放。C++的類,構造函數和析構函數會在創建和銷毀時自動調用到。利用好這兩點是實現智能指針的基礎

智能指針是類

初步設計,智能指針是一個類,類有一個成員指針,能指向任意的object,所以是一個模板類

template <typename T>
class sp {
    sp()  {}
    ~sp() {}
    private:
        T* ptr;
}

對于指針未初始化 只需要在構造進行處理即可

template <typename T>
class sp {
    sp() :m_ptr(0){}
    ~sp() {}
    private:
        T* m_ptr;;
}

而delete后未置為NULL,還要結合計數問題來考慮,因為會有多個指針指向同一個地址

計數問題

智能指針如何判斷對象的內存不在需要呢,在很多領域有引用計數的概念,及當沒有指針指向該內存時,就可以認為該內存對象不需要了。
那么該如果計數呢,是否能由智能指針來計數?
明顯是很難做到的,如下圖,兩個智能指針的內存空間是獨立的,智能指針持有計數變量,各指針變量之間很難同步


sp.png

另一種方法是object自己計數,這需要object繼承一個類


sp.png

當sp類創建時,調用incStrong方法增加計數,當sp釋放時,調用decStrong方法減少計數,當mCount為0時則刪除object內存.
根據使用方法

sp<MediaPlayer>(p);
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);

sp類需要重載=運算符和復制構造函數。因此sp類的定義如下

template <typename T>
class sp {
    sp() :m_ptr(0){}
    ~sp() {}

    sp(T* other); 
    sp& operator = (T* other);
    private:
        T* m_ptr;;
}

sp類在構造函數中調用incStrong增加計數,在析構函數中調用decStrong減少引用計數

template<typename T>
sp<T>::sp(T* other)
: m_ptr(other)
  {
    if (other) other->incStrong(this);
  }

template<typename T>
sp<T>& sp<T>::operator = (T* other)
{
    if (other) other->incStrong(this);
    if (m_ptr) m_ptr->decStrong(this);
    m_ptr = other;
    return *this;
}
template<typename T>
sp<T>::~sp()
{
    if (m_ptr) m_ptr->decStrong(this);
}

重載=運算符是考慮同一對象重復賦值的情況。

template <class T>
class LightRefBase
{
public:
    inline LightRefBase() : mCount(0) { }
    inline void incStrong(__attribute__((unused)) const void* id) const {
        __sync_fetch_and_add(&mCount, 1);
    }
    inline void decStrong(__attribute__((unused)) const void* id) const {
        if (__sync_fetch_and_sub(&mCount, 1) == 1) {
            delete static_cast<const T*>(this);
        }
    }
    //! DEBUGGING ONLY: Get current strong ref count.
    inline int32_t getStrongCount() const {
        return mCount;
    }

    typedef LightRefBase<T> basetype;

protected:
    inline ~LightRefBase() { }

private:
    friend class ReferenceMover;
    inline static void moveReferences(void*, void const*, size_t,
            const ReferenceConverterBase&) { }

private:
    mutable volatile int32_t mCount;
};

LightRefBase 的decStrong當引用計數為1時,會將自身delete掉。 LightRefBase是Andorid輕量級智能指針的實現方式。而MediaPlayer繼承的是RefBase,會比較復雜,涉及到弱指針轉強指針的問題。不過原理是一樣的。

強指針

強指針 跟輕量級指針 使用的sp類是一樣的,不同的是object繼承的類是RefBase。看一下MediaPlayer的繼承關系

class MediaPlayer : public BnMediaPlayerClient,
                    public virtual IMediaDeathNotifier
class IMediaDeathNotifier: virtual public RefBase

強指針的原理其實跟輕指針一樣,都是引用計數。

class RefBase
{
public:
            void            incStrong(const void* id) const;
            void            decStrong(const void* id) const;

    class weakref_type
    {
    public:
        RefBase*            refBase() const;

        void                incWeak(const void* id);
        void                decWeak(const void* id);

    };

   weakref_type*   createWeak(const void* id) const;
   weakref_type*   getWeakRefs() const;
   typedef RefBase basetype;

protected:
                            RefBase();
    virtual                 ~RefBase();

    //! Flags for extendObjectLifetime()
    enum {
        OBJECT_LIFETIME_STRONG  = 0x0000,
        OBJECT_LIFETIME_WEAK    = 0x0001,
        OBJECT_LIFETIME_MASK    = 0x0001
    };

private:

        weakref_impl* const mRefs;
};

RefBase 嵌套了內部類weakref_type,大部分的工作其實都是weakref_type完成的。RefBase 還有一個成員變量 weakref_impl* const mRefs, 從名字看 weakref_impl 繼承自weakref_type,是它的實現類。

class RefBase::weakref_impl : public RefBase::weakref_type
{
public:
    std::atomic<int32_t>    mStrong;   //強引用計數
    std::atomic<int32_t>    mWeak;   //弱引用計數
    RefBase* const          mBase;
    std::atomic<int32_t>    mFlags;

weakref_impl(RefBase* base)
        : mStrong(INITIAL_STRONG_VALUE)
        , mWeak(0)
        , mBase(base)
        , mFlags(0)
        , mStrongRefs(NULL)
        , mWeakRefs(NULL)
        , mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)
        , mRetain(false)
    {
    }
}

先來了解下強指針會調用到的incStrong和decStrong

void RefBase::incStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->incWeak(id);  //增加弱引用計數器   
    refs->addStrongRef(id);  //調試目的,可以不管

   //C++11 std::atomic 成員函數
   //T fetch_add (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
   //將原子對象的封裝值加 val,并返回原子對象的舊值(適用于整形和指針類型的 std::atomic 特化版本),整個過程是原子的。sync 參數指定內存序:
    const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed); //強引用計數加1

    if (c != INITIAL_STRONG_VALUE)  {
        return;
    }

   //mStrong在構造函數初始化時被賦值為INITIAL_STRONG_VALUE,
   //所以第一次增加時還需要
   //減去INITIAL_STRONG_VALUE,mStrong的值才為1
    int32_t old __unused = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE, std::memory_order_relaxed);
    // A decStrong() must still happen after us.
    ALOG_ASSERT(old > INITIAL_STRONG_VALUE, "0x%x too small", old);
    refs->mBase->onFirstRef(); //RefBase為空方法,可由子類繼承實現
}

void RefBase::decStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->removeStrongRef(id);  //調試使用,可以不理
    const int32_t c = refs->mStrong.fetch_sub(1, std::memory_order_release); //強引用計數減1

    //之前的引用計數只剩下1時,會刪除object內存
    if (c == 1) {
        std::atomic_thread_fence(std::memory_order_acquire);
        refs->mBase->onLastStrongRef(id); //RefBase為空方法,可由子類繼承實現
        int32_t flags = refs->mFlags.load(std::memory_order_relaxed);
        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
            delete this;
            // The destructor does not delete refs in this case.
        }
    }

    refs->decWeak(id); //減少弱引用計數
}

對于強指針,主要關注的是對強引用計數mStrong的操作,原理跟Light Pointer。 這里還有有對弱指針進行操作,在下面再對wp進行介紹

弱指針

強指針的使用會帶來另一個問題,對象互相引用,比如

class A {
    B *b;
}

class B {
    A *a;
}

如果A 指向了B,B又指向了,則會帶來類似死鎖的問題。解決的方法就是一個應用采用強指針,另一個采用弱指針,當強指針計數為0時,無論弱指針計數是否為0,都可以delete掉該內存。但這又有一個新問題:使用弱指針的一方訪問的對象已經被刪除了,這會導致野指針的問題。所以又做了一項規定弱指針必須先升級為強指針才能訪問其指向的對象

template <typename T>
class wp
{
public:
    typedef typename RefBase::weakref_type weakref_type;

    inline wp() : m_ptr(nullptr), m_refs(nullptr) { }

    wp(T* other);  // NOLINT(implicit)
    ~wp();

    // Assignment

    wp& operator = (T* other);


    void set_object_and_refs(T* other, weakref_type* refs);

    // promotion to sp
    sp<T> promote() const;   //提升為強指針

private:
    template<typename Y> friend class sp;
    template<typename Y> friend class wp;

    T*              m_ptr;
    weakref_type*   m_refs;
};

弱指針有兩個成員指針 m_ptr 指向object,m_refs指向了RefBase中的weakref_type.先看一下其構造函數

template<typename T>
wp<T>::wp(T* other)
    : m_ptr(other)
{
    m_refs = other ? m_refs = other->createWeak(this) : nullptr;
}

RefBase::weakref_type* RefBase::createWeak(const void* id) const
{
    mRefs->incWeak(id);
    return mRefs;
}

template<typename T>
wp<T>::~wp()
{
    if (m_ptr) m_refs->decWeak(this);
}

當一個弱指針被指向某一object時,會調用到createWeak, createWeak會調用incWeak增加弱引用計數,其他的賦值方法都是一樣,沒必要列出來。析構時會調用decWeak減少弱引用計數

接下來看兩個操作弱引用計數的方法

void RefBase::weakref_type::incWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    impl->addWeakRef(id);
    const int32_t c __unused = impl->mWeak.fetch_add(1,
            std::memory_order_relaxed);
    ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
}


void RefBase::weakref_type::decWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    impl->removeWeakRef(id);
    const int32_t c = impl->mWeak.fetch_sub(1, std::memory_order_release);
    LOG_ALWAYS_FATAL_IF(BAD_WEAK(c), "decWeak called on %p too many times",
            this);
    if (c != 1) return;
    atomic_thread_fence(std::memory_order_acquire);

    int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
    if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
        // This is the regular lifetime case. The object is destroyed
        // when the last strong reference goes away. Since weakref_impl
        // outlives the object, it is not destroyed in the dtor, and
        // we'll have to do it here.
        if (impl->mStrong.load(std::memory_order_relaxed)
                == INITIAL_STRONG_VALUE) {
            // Decrementing a weak count to zero when object never had a strong
            // reference.  We assume it acquired a weak reference early, e.g.
            // in the constructor, and will eventually be properly destroyed,
            // usually via incrementing and decrementing the strong count.
            // Thus we no longer do anything here.  We log this case, since it
            // seems to be extremely rare, and should not normally occur. We
            // used to deallocate mBase here, so this may now indicate a leak.
            ALOGW("RefBase: Object at %p lost last weak reference "
                    "before it had a strong reference", impl->mBase);
        } else {
            // ALOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase);
            delete impl;
        }
    } else {
        // This is the OBJECT_LIFETIME_WEAK case. The last weak-reference
        // is gone, we can destroy the object.
        impl->mBase->onLastWeakRef(id);
        delete impl->mBase;
    }
}

在減弱指針計數后,當c(弱指針計數)不為1,則直接返回. 當c為1,做了一些判斷:
首先 對flag做了判斷,

//! Flags for extendObjectLifetime()
    enum {
        OBJECT_LIFETIME_STRONG  = 0x0000,
        OBJECT_LIFETIME_WEAK    = 0x0001,
        OBJECT_LIFETIME_MASK    = 0x0001
    };

enum是目標對象的生命周期,每個目標對象可以通過extendObjectLifetime來修改其生命周期(我也不太懂這里)。如果不去修改,默認情況下flag都為OBJECT_LIFETIME_STRONG,故會進到if判斷里面去。
對于這個if判斷的邏輯,這里我也不是很理解,只能直白地說下字面意思,在增加或減少強指針計數時,會同時整加或減少弱指針計數,而在對弱指針計數的操作則不會同時對強指針計數進行操作,所以
弱指針計數 >= 強指針計數
如果impl->mStrong 為INITIAL_STRONG_VALUE,表示從沒被強引用過則不做任何操作,有可能對象之前被弱引用,但是已經被適當地銷毀了。所以不用做任何事情。如果impl->mStrong 不為INITIAL_STRONG_VALUE,即弱指針計數和 強指針計數同時為0,這時候刪除impl, 即RefBase 的 weakref_impl* const mRefs對象。

關于sp和wp,
sp 的incStrong 會同時增加sp和wp計數, wp計數通過incWeak操作
wp 的incWeak 只會增加wp計數
sp 的decStrong 會減小sp計數和wp計數,當sp計數為0時會delete object,object是繼承RefBase的對象,wp計數通過decWeak操作
wp 的decWeak只會減小wp計數, 如果wp計數為0,如果object從未被強引用則不做任何操作(Android 10的邏輯,之前好像會delete impl->mBase,即object對象),否則會delete RefBase 的 weakref_impl* const mRefs對象

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容