普通指針的使用隱患和智能指針的解決辦法
普通指針在使用過程中的三個隱患:
- 指針沒有初始化。
- 指針沒有釋放。
- 釋放後沒有置空。
智能指針的解決辦法:
- 構造函數中初始化。
- 使用計數器,計數器爲0時釋放。
- 將指針封裝在智能指針中,析構時釋放。
標準庫中的智能指針爲shared_ptr,而android Binder相關代碼使用wp,sp作爲智能指針。
android中的智能指針
計數器由object自身持有
計數器並非由智能指針擁有(這是我之前一直弄錯的地方)。如果計數器由智能指針擁有,那麼當有多個智能指針都由該object初始化的時候,它們的計數值不能共享,也就無法確定該object究竟被引用了多少次。
解決辦法就是讓計數器由object自身持有,這樣只要在初始化智能指針的時候更新object持有的計數器,計數值就自然而然地被所有智能指針共享了。每個智能指針都可以從object的計數器中知道該object還剩多少個引用,從而決定是否釋放。
android中定義了統一的父類“RefBase”和“LightRefBase”,所有有計數要求的類型都繼承於這兩個類。
這是sp和wp的實現方法,暫不清楚stl中是怎麼實現智能指針的。
另外,sp中重載了->操作符,sp指針->的效果等價於原指針->的效果
弱指針wp的構造
弱指針是爲了避免智能指針引用中的“死鎖”而產生的,“死鎖”現象即兩個類的成員變量中均有指向對方的智能指針,這樣就會導致這兩個類的智能指針引用計數永遠都不爲零。
弱引用有兩個特點:
- 當強引用爲0時,無論弱引用是多少,對象都會被delete(android中有例外?)。
- 弱指針必須先升級成強指針,才能訪問它所指的對象。
wp的參數爲指針的構造函數如下:
//frameworks\rs\cpp\util\RefBase.h
template<typename T>
wp<T>::wp(T* other)
: m_ptr(other)
{
if (other) m_refs = other->createWeak(this);
}
構造函數把指針賦給自己的成員m_ptr,然後以自身作爲參數調用了createWeak函數,createWeak函數屬於前面提到的父類RefBase:
//android-7.1.1_r1\system\core\libutils\RefBase.cpp
RefBase::weakref_type* RefBase::createWeak(const void* id) const
{
mRefs->incWeak(id);
return mRefs;
}
createWeak將wp指針作爲id參數調用了mRefs的incWeak函數(從之後的代碼來看,id僅是用來調試用的,這裏可以忽略),並將mRef返回。mRef將被賦值給wp的m_refs。
mRef的類型是weakref_impl,而wp的m_refs成員的類型是weakref_type,weakref_impl繼承weakref_type,從名字就可以看到前者是後者的實現類,可以把這兩者當作相同類型看待。
weakref_impl非常重要,截取weakref_impl的定義如下:
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;
#if !DEBUG_REFS
weakref_impl(RefBase* base)
: mStrong(INITIAL_STRONG_VALUE)
, mWeak(0)
, mBase(base)
, mFlags(0)
{
}
...
}
其中的mStrong和mWeak就是強弱引用計數,mBase是weakref_impl的從屬類RefBase的指針,之所以這樣設計是爲了便於釋放RefBase。從這裏可以看出weakref_impl即是統一父類RefBase的計數器。接下來看incWeak函數:
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);
}
第一行代碼用於強制類型轉換,將weakref_type強制轉化成weakref_impl,第二行是debug代碼,release版本中addWeakRef並沒有實現。第三行即是將weakref_impl的mWeak+1,這裏實現了計數器的自增。
這裏的寫法非常奇特,是我以前沒見過的寫法,incWeak屬於weakref_type,其內部實現卻調用了子類weakref_Impl的函數,不知道爲什麼要這樣設計。(有點好奇這樣的寫法是怎麼通過編譯的)
各個類的關係如下:
強指針sp的構造
強指針的代碼比弱指針的代碼簡單,它的構造函數如下
//\frameworks\rs\cpp\util\StrongPointer.h
template<typename T>
sp<T>::sp(T* other)
: m_ptr(other)
{
if (other) other->incStrong(this);
}
直接調用了”統一父類“RefBase的incStrong函數。這裏注意和弱指針的不同,弱指針的incWeak在weakref_type類中,而強指針的incStrong在RefBase類中。繼續看incStrong:
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);
ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs);
#if PRINT_REFS
ALOGD("incStrong of %p from %p: cnt=%d\n", this, id, c);
#endif
if (c != INITIAL_STRONG_VALUE) {
return;
}
int32_t old = 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();
}
就是強弱引用計數自增。後面的代碼是處理第一次引用的情況的,可以忽略。
強弱指針的刪除
引用的自減由decStrong和decWeak完成,和前面一樣,decStrong屬於RefBase,而decWeak屬於weakref_type。
強指針的析構會調用decStrong先使強引用減一,再調用decWeak使弱引用減一(注意先後順序)。弱指針的析構會調用decWeak使弱引用減一。當強弱引用爲0時,就要判斷是否要釋放指針和計數器,即RefBase和weakref_impl。考慮以下幾種情況:
- 強引用爲0,弱引用不爲0。強指針的析構可能會導致該情況的發生。此時decStrong會釋放RefBase,但decWeak不會釋放weakref_impl,即wp的m_ref成員依然有效。
- 弱引用爲0,強引用爲0,且指針曾被強引用過。這種情況只可能發生在情況1之後,此時RefBase已被釋放,decWeak只需釋放weakref_impl即可。
- 弱引用爲0,強引用爲0,且指針未被強引用過。這種情況下,由於沒有強引用,也就沒有強指針的析構,decStrong不會被調用,故RefBase釋放的任務由decWeak完成。而RefBase的析構函數中,當弱引用爲0時,會釋放weakref_impl,這就完成了指針的計數器的釋放。
雖然weakref_impl是由RefBase創建的,但在情況1和情況2中,它卻是由自己完成自己的釋放(在weakref_type的incWeak中完成釋放,難道這就是分weakref_impl和weakref_type的理由?),只有在情況3中它才被RefBase釋放。這是由於強引用爲0時,弱引用不一定爲0,所以此時計數器不能隨着RefBase釋放。只有當強弱引用都爲0時才能釋放計數器。
理解了以上概念後,再看代碼就不難了:
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 PRINT_REFS
ALOGD("decStrong of %p from %p: cnt=%d\n", this, id, c);
#endif
ALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs);
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.
}
}
// Note that even with only strong reference operations, the thread
// deallocating this may not be the same as the thread deallocating refs.
// That's OK: all accesses to this happen before its deletion here,
// and all accesses to refs happen before its deletion in the final decWeak.
// The destructor can safely access mRefs because either it's deleting
// mRefs itself, or it's running entirely before the final mWeak decrement.
refs->decWeak(id);
}
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;
}
}
參考資料:深入理解Android內核設計思想