Android 強弱指針分析

Android 強弱指針分析

在C C++ 語言中,內存的管理歷來是一個比較難的問題,在java 中內存new 的對象由jvm 虛擬機自動回收。在Android 上面提供了sp 和wp 兩種類型的指針,管理new 出來的對象,能夠自動的回收對象,專業于業務減輕在內存管理上的負擔。

實現對對象的管理通常的做法是使用引用計數,每增加一次引用引用計數增加一,當引用計數為0時,銷毀這個對象。引用計數可以放在對象內部,也可以放在外部。Android的做法是放在對象內部。

在Android 7.0 版本上相關的代碼及位置在:

  • system/core/libutils/RefBase.cpp
  • system/core/include/utils/RefBase.h
  • system/core/include/utils/StrongPointer.h

C++ 11

在C++ 11 中引入了大量的新特性,使一些開發變得簡單。在引用計數的變量上使用了
std::atomic模板類:template <class T> struct atomic;提供原子操作,在原來的版本上使用的是Android平臺封裝的API。
主要用到兩個API:fetch_add 和fetch_sub,用于加1和減1。

integral fetch_add(integral, memory_order = memory_order_seq_cst) volatile;
integral fetch_add(integral, memory_order = memory_order_seq_cst);
integral fetch_sub(integral, memory_order = memory_order_seq_cst) volatile;
integral fetch_sub(integral, memory_order = memory_order_seq_cst);

用于線程的同步的API,沒有找到具體的資料。

atomic_thread_fence

參考C++11 并發指南六(atomic 類型詳解三 std::atomic (續))

這兩個API的最后一個參數是std::memory_order類型。主要是內存模型參數,可以調整代碼的執行順序,告訴編譯器的優化方法,比如如果GCC 加了O2參數,會對代碼的執行順序做一定的調整,但是在多線程中就會帶來一定的影響,出現錯誤,內存模型參數可以指定編譯器的優化方式,限定多個原子語句的執行順序。

C++11 并發指南七(C++11 內存模型一:介紹)

/*
std::memory_order
C++  Atomic operations library 
Defined in header <atomic>
*/
enum memory_order {
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
};

主要用到兩個:

  1. std::memory_order_relaxed:線程內順序執行,線程間隨意。
  2. std::memory_order_seq_cst:多線程保持順序一致性,像單線程一樣的執行。

參考:
std::memory_order

這段內容據說完全搞懂的全球屈指可數。

主要的類

1. RefBase

需要能夠自動管理內存的對象都要繼承這個類,在RefBase內部有int 型的引用計數。實際是通過weakref_impl類型的mRefs管理。

RefBase::RefBase() : mRefs(new weakref_impl(this))
{
}

通過 ==void incStrong(const void* id) const== 函數增加引用計數,

  1. refs->incWeak(id); 增加弱引用計數。
  2. 增加強引用計數,如果 const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed);
    返回值c 為初始值INITIAL_STRONG_VALUE,執行onFirstRef。onFirstRef 函數體為空,可以重載做一些初始化工作。

通過 ==void decStrong(const void* id) const== 減少引用計數。

  1. 減少強引用計數 const int32_t c = refs->mStrong.fetch_sub(1, std::memory_order_release);
  2. 如果從c==1, 先做一些清理工作:onLastStrongRef 接著刪除 delete this
  3. 如果不為1,refs->decWeak(id);
void RefBase::incStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->incWeak(id);
    
    refs->addStrongRef(id);
    const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed);
   
    if (c != INITIAL_STRONG_VALUE)  {
        return;
    }

    int32_t old = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE,
            std::memory_order_relaxed);

    refs->mBase->onFirstRef();
}

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);

    if (c == 1) {
        std::atomic_thread_fence(std::memory_order_acquire);
        refs->mBase->onLastStrongRef(id);
        int32_t flags = refs->mFlags.load(std::memory_order_relaxed);
        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
            delete this;
            // Since mStrong had been incremented, the destructor did not
            // delete refs.
        }
    }

    refs->decWeak(id);
}

2. RefBase::weakref_type

RefBase::weakref_type 主要定義了兩個函數: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);
    ALOG_ASSERT(c >= 1, "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
        // outlive 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) {
            // Special case: we never had a strong reference, so we need to
            // destroy the object now.
            delete 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;
    }
}

==需要注意的是在執行delete 是使用了mFlags 這個變量,在下邊可以看到這個變量的定義。==

3. RefBase::weakref_impl

RefBase::weakref_impl 繼承自RefBase::weakref_type 真實的引用計數使用RefBase的內部類RefBase::weakref_impl管理, 有四個內部變量:mStong mWeak mBase, mFlags. mStrong 和sp 配合,負責強引用計數;mWeak 和wp 配合,負責弱引用計數。

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;
}

// mFlags定義
// OBJECT_LIFETIME_STRONG 為默認值,對象以強引用計數管理生命周期
// OBJECT_LIFETIME_WEAK             對象以弱引用計數管理生命周期 

    enum {
        OBJECT_LIFETIME_STRONG  = 0x0000,  
        OBJECT_LIFETIME_WEAK    = 0x0001,
        OBJECT_LIFETIME_MASK    = 0x0001
    };

4. sp 為強指針

負責強引用計數管理,內部有m_ptr 指針保存RefBase對象,重載了 “=”操作符,調用m_ptr的==incStrong==操作引用計數+1, 析構的時候調用==decStrong== -1.

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

template<typename T>
sp<T>::~sp() {
    if (m_ptr)
        m_ptr->decStrong(this);
}

5. wp 是弱指針

負責對象之間的解引用。如果子類保存有父指針,父類保存有子指針,在析構的時候子類先析構,但是父類保有子類的引用,導致引用計數不為0,無法刪除子類;然后父類析構,子類保有父類的引用計數,父類也無法刪除,這時候需要使用wp避免出現這種情況。和sp 一樣 wp重載了 操作符“=” 調用 incWeak, 在析構的時候 decWeak。
在RefBase 里面有兩個變量mStrong, mWeak 分別保存強弱引用計數,只要強引用計數為0,強制delete。

舉個例子:
我們定義兩個類A B, 后析構的B使用wp類型的指針保存A,在析構的時候如果弱引用類型不為0,只要強引用類型為0,強制delete。A先析構,強引用類型為0,軟引用類型為1,強制delete, 這樣B的強引用類型也變為1,B析構的時候執行完del 后強引用類型為0,delete

template<typename T>
wp<T>& wp<T>::operator = (const wp<T>& other)
{
    weakref_type* otherRefs(other.m_refs);
    T* otherPtr(other.m_ptr);
    if (otherPtr) otherRefs->incWeak(this);
    if (m_ptr) m_refs->decWeak(this);
    m_ptr = otherPtr;
    m_refs = otherRefs;
    return *this;
}

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

6. 強弱指針的對比

  1. 通過類圖可以發現,強指針實現了 “.” "->" 操作符的重載,因此sp 可以直接方位類成員,而wp 卻不能,
  2. 但是wp 可以轉化為sp
template<typename T>
sp<T> wp<T>::promote() const
{
    sp<T> result;
    if (m_ptr && m_refs->attemptIncStrong(&result)) {
        result.set_pointer(m_ptr);
    }
    return result;
}

具體的類圖如下:


Android 指針類圖
Android 指針類圖

二 移植到PC

為了編譯研究測試代碼,把這是三個文件移植到PC環境下。Andrioid7.0代碼針對C++ 11 做了修改,在API的跨平臺編譯上做的非常好,沒什么大的改動,注釋掉部分Android的Log 代碼就編譯通過了。在這里也贊一下 C++ 11。平臺為MAC,IDE為CLion 2016.3,編譯使用CMake。

code

三 LightRefBase

在不考慮類互相引用的情況下,引用計數比較簡單,Android提供了LightRefBase模板類
內部采用 mutable std::atomic<int32_t> mCount; 保存引用計數。

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

    typedef LightRefBase<T> basetype;

protected:
    inline ~LightRefBase() { }

private:
    friend class ReferenceMover;
    inline static void renameRefs(size_t n, const ReferenceRenamer& renamer) { }
    inline static void renameRefId(T* ref,
            const void* old_id, const void* new_id) { }

private:
    mutable std::atomic<int32_t> mCount;
};

最開始的測試代碼如下:

class LightRefBaseTest: public LightRefBase<LightRefBaseTest>{
public:
    LightRefBaseTest(){std::cout << "Hello, LightRefBaseTest!" << std::endl;};
    ~LightRefBaseTest(){std::cout << "Hello, ~LightRefBaseTest()!" << std::endl;};
};


int main() {
    std::cout << "Hello, World!" << std::endl;
    LightRefBaseTest lightTest;
    return 0;
}

/*
結果:

Hello, World!
Hello, LightRefBaseTest!
Hello, ~LightRefBaseTest()!

Process finished with exit code 0
*/

修改下LightRefBaseTest lightTest 為:

int main() {
    std::cout << "Hello, World!" << std::endl;
    LightRefBaseTest* lightTest = new LightRefBaseTest();
    return 0;
}

/*
結果 LightRefBaseTest沒有析構:

Hello, World!
Hello, LightRefBaseTest!

Process finished with exit code 0
*/

再修改下,使用sp 指針,LightRefBaseTest又析構了:

int main() {
    std::cout << "Hello, World!" << std::endl;
    sp<LightRefBaseTest> sp1 = new LightRefBaseTest();
    return 0;
}

/*
看下結果,LightRefBaseTest析構了:

Hello, World!
Hello, LightRefBaseTest!
Hello, ~LightRefBaseTest()!

Process finished with exit code 0
*/

看下互相引用的情況:

class LightRefBaseTest2;

class LightRefBaseTest: public LightRefBase<LightRefBaseTest>{
public:
    LightRefBaseTest(){std::cout << "Hello, LightRefBaseTest!" << std::endl;};
    ~LightRefBaseTest(){std::cout << "Hello, ~LightRefBaseTest()!" << std::endl;};
    void setPointer(sp<LightRefBaseTest2> pointer){mPointer = pointer;};
private:
    sp<LightRefBaseTest2>  mPointer;
};


class LightRefBaseTest2: public LightRefBase<LightRefBaseTest>{
public:
    LightRefBaseTest2(){std::cout << "Hello, LightRefBaseTest2!" << std::endl;};
    ~LightRefBaseTest2(){std::cout << "Hello, ~LightRefBaseTest2()!" << std::endl;};
    void setPointer(sp<LightRefBaseTest> pointer){mPointer = pointer;};

private:
    sp<LightRefBaseTest>  mPointer;
};

int main() {
    std::cout << "Hello, World!" << std::endl;
//    LightRefBaseTest* lightTest = new LightRefBaseTest();
    sp<LightRefBaseTest> sp1 = new LightRefBaseTest();
    sp<LightRefBaseTest2> sp2 = new LightRefBaseTest2();
    sp1->setPointer(sp2);
    sp2->setPointer(sp1);

    return 0;
}
/* 兩個類都沒有析構。LightRefBaseTest析構的時候由于LightRefBaseTest2持有它的引用,導致不能夠調用delete, 同理LightRefBaseTest2也不能夠析構
Hello, World!
Hello, LightRefBaseTest!
Hello, LightRefBaseTest2!

Process finished with exit code 0
*/

LightRefBase 已經很完美的解決了C++ new 對象的管理問題,但是有一個致命的缺陷,不能解決類之間的相互引用。

wp sp RefBase 配合使用。

image
image

第一種析構: 析構路線圖如圖中 A線 所以

class SubRefBaseTest;
class RefBaseTest: public RefBase{
public:
    RefBaseTest(){std::cout << "Hello, RefBaseTest!" << std::endl;};
    ~RefBaseTest(){std::cout << "Hello, ~RefBaseTest()!" << std::endl;};
    void setPointer(sp<SubRefBaseTest> pointer){
        mPointer = pointer;
    };

private:
    sp<SubRefBaseTest> mPointer;
};

class SubRefBaseTest: public RefBase{
public:
    SubRefBaseTest(){ std::cout << "Hello, SubRefBaseTest!" << std::endl;};
    ~SubRefBaseTest(){std::cout << "Hello, ~SubRefBaseTest()!" << std::endl;};
    void setPointer(sp<RefBaseTest> pointer){
        mPointer = pointer;
    };
private:
    sp<RefBaseTest> mPointer;
};

int main() {
    std::cout << "Hello, World!" << std::endl;
    sp<RefBaseTest>  refBaseTest = new RefBaseTest();
    sp<SubRefBaseTest>  subRefBaseTest = new SubRefBaseTest();

    return 0;
}

/*
Hello, World!
Hello, RefBaseTest!
Hello, SubRefBaseTest!
Hello, ~SubRefBaseTest()!
Hello, ~RefBaseTest()!

Process finished with exit code 0
*/
int main() {
    std::cout << "Hello, World!" << std::endl;
    sp<RefBaseTest>  refBaseTest = new RefBaseTest();
    sp<SubRefBaseTest>  subRefBaseTest = new SubRefBaseTest();
    refBaseTest->setPointer(subRefBaseTest);
    subRefBaseTest->setPointer(refBaseTest);
    
    return 0;
}

/* 還是無法析構
Hello, World!
Hello, RefBaseTest!
Hello, SubRefBaseTest!

Process finished with exit code 0
*/
class RefBaseTest: public RefBase{
public:
    RefBaseTest(){std::cout << "Hello, RefBaseTest!" << std::endl;};
    ~RefBaseTest(){std::cout << "Hello, ~RefBaseTest()!" << std::endl;};
    void setPointer(wp<SubRefBaseTest> pointer){
        mPointer = pointer;
    };

private:
    wp<SubRefBaseTest> mPointer;
};

/* 
修改RefBaseTest 引用類型為wp, 正常析構了。
在這里用一個隱式的類型轉化,refBaseTest->setPointer(subRefBaseTest);
將強指針轉為弱指針。看下重載的 “=” 操作符 弱引用加一。 
Hello, World!
Hello, RefBaseTest!
Hello, SubRefBaseTest!
Hello, ~SubRefBaseTest()!
Hello, ~RefBaseTest()!

Process finished with exit code 0
*/

template<typename T>
wp<T>& wp<T>::operator = (const sp<T>& other)
{
    weakref_type* newRefs =
        other != NULL ? other->createWeak(this) : 0;
    T* otherPtr(other.m_ptr);
    if (m_ptr) m_refs->decWeak(this);
    m_ptr = otherPtr;
    m_refs = newRefs;
    return *this;
}

第二種析構

如圖中線路B所示

int main() {
    std::cout << "Hello, World!" << std::endl;

    wp<RefBaseTest> wp1 = new RefBaseTest();
    return 0;
}

C++ 11 的智能指針

C++ 智能指針# Android 強弱指針分析

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

推薦閱讀更多精彩內容