Android中的智慧指標:sp和wp
原文:
連結:http://blog.csdn.net/DroidPhone/article/details/5799792經常會在android的framework程式碼中發現sp<xxx>和wp<xxx>這樣的指標,平時看的時候都把他當成一個普通的指標封裝過掉了,這幾天終於忍不住了,想深入瞭解一下。
相關的程式碼:
frameworks/base/include/utils/RefBase.h
frameworks/base/libs/utils/RefBase.cpp
sp和wp都是一個模板類,看一下sp類的定義:
- template <
- class sp
- {
- public:
- typedeftypename RefBase::weakref_type weakref_type;
- inline sp() : m_ptr(0) { }
- sp(T* other);
- sp(const sp<T>& other);
- ~sp();
- ......
- private:
- // Optimization for wp::promote().
- sp(T* p, weakref_type* refs);
- T* m_ptr;
- };
可以看到他確實封轉了一個原生指標T* m_ptr. 再看一下其中一個建構函式和解構函式:
- template<typename T>
- sp<T>::sp(T* other)
- : m_ptr(other)
- {
- if (other) other->incStrong(this);
- }
- template<typename T>
- sp<T>::~sp()
- {
- if (m_ptr) m_ptr->decStrong(this);
- }
咋一看好奇怪,因為在建構函式中呼叫了incStrong(),在解構函式中呼叫的decStrong(),顯然是管理引用計數的函式,但是sp類的中並沒有定義這兩個函式,這兩個函式是在RefBase類中定義的,由此可以得出結論:
要想使用sp<T>或者wp<T>, T必需要繼承RefBase類才行。
RefBase的靜態關係如下:
其中weakref_type是RefBase的內嵌類,weakref_impl則是weakref_type的子類,RefBase的大部分工作都是交由weakref_impl類來完成,通過RefBase的成員變數weakref_impl* const mRefs。檢視其中一個sp的建構函式:
- template<typename T>
- sp<T>::sp(T* other)
- : m_ptr(other)
- {
- if (other) other->incStrong(this);
- }
建立sp<xxx>的動態關係如下:
sp<T>
--> RefBase : incStrong()
-->weakref_impl : addStrongRef()
-->android_atomic_inc(&refs->mStrong)
可見當一個普通指標變成一個sp指標後,將會由RefBase類維護該指標的引用計數,當引用為零時則自動釋放該指標指向的記憶體:
- void RefBase::decStrong(constvoid* id) const
- {
- weakref_impl* const refs = mRefs;
- refs->removeStrongRef(id);
- const int32_t c = android_atomic_dec(&refs->mStrong);
- if (c == 1) {
- const_cast<RefBase*>(this)->onLastStrongRef(id);
- if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
- delete this; //引用為0,銷燬
- }
- }
- refs->removeWeakRef(id);
- refs->decWeak(id);
- }
wp<xxx>是怎麼一回事?
wp其實是弱指標的意思,wp<T>型別不能直接對型別T進行操作,要想對T進行某種操作,必需把wp升級為sp指標,使用promote()來實現升級:
wp<T> weakp= new T();
sp<T> t = weakp.promote();
wp可能會在弱引用計數不為0的情況下被銷燬,執行如下程式碼:
- class WPTest : public RefBase {
- public:
- WPTest(){
- LOGD("WPTest constructor");
- }
- virtual ~WPTest() {
- LOGD("WPTest destructor");
- }
- virtualvoid onFirstRef() {
- LOGD("first weak ptr ref callback");
- }
- virtualvoid onLastStrongRef(constvoid* id) {
- LOGD("last strong ptr ref callback");
- }
- virtualvoid onLastWeakRef(constvoid* id) {
- LOGD("last weak ptr ref callback");
- }
- };
- int main()
- {
- WPTest *T = new WPTest();
- {
- wp<WPTest> weakp(T);
- {
- LOGD("promote to strong ptr.../n");
- sp<WPTest> strongp = weakp.promote();
- LOGD("strong ptr's lifetime is just about to finish .../n");
- }
- LOGD("weak ptr's lifetime is just about to finish .../n");
- }
- LOGD("weak ptr is out of scope./n");
- return 0;
- }
程式列印的結果是:
D/sp-wp-sample( 225): WPTest constructor
D/sp-wp-sample( 225): promote to strong ptr...
D/sp-wp-sample( 225): first weak ptr ref callback
D/sp-wp-sample( 225): strong ptr's lifetime is just about to finish ...
D/sp-wp-sample( 225): last strong ptr ref callback
D/sp-wp-sample( 225): WPTest destructor
D/sp-wp-sample( 225): weak ptr's lifetime is just about to finish ...
D/sp-wp-sample( 225): weak ptr is out of scope.
由此可見雖然wp<WPTest >的生命週期還沒有結束,但是因為升級為sp<WPTest >後,sp<WPTest >的強引用計數為0,導致WPTest 被銷燬,當強引用為0而弱引用不為0時,WPTest 銷燬時,基類RefBase的mRefs指向的weakref_impl類並沒有釋放,從而保證了弱引用可以繼續起作用,這點可以從RefBase的解構函式中看出來:
- RefBase::~RefBase()
- {
- // LOGV("Destroying RefBase %p (refs %p)/n", this, mRefs);
- if (mRefs->mWeak == 0) {
- // LOGV("Freeing refs %p of old RefBase %p/n", mRefs, this);
- delete mRefs;
- }
- }
不過也可以改變這一行為,我們修改一下WPTest的建構函式:
- WPTest(){
- LOGD("WPTest constructor");
- extendObjectLifetime(OBJECT_LIFETIME_WEAK);
- }
這時的列印結果是:
D/sp-wp-sample( 217): WPTest constructor
D/sp-wp-sample( 217): promote to strong ptr...
D/sp-wp-sample( 217): first weak ptr ref callbac
D/sp-wp-sample( 217): strong ptr's lifetime is j
D/sp-wp-sample( 217): last strong ptr ref callba
D/sp-wp-sample( 217): weak ptr's lifetime is j
D/sp-wp-sample( 217): last weak ptr ref callback
D/sp-wp-sample( 217): WPTest destructor
D/sp-wp-sample( 217): weak ptr is out of scope.
可以看出現在只有當強引用和弱引用的計數都為0時,WPTest物件才會被銷燬。
原文二:
androidsp wp 【轉】
在android 中可以廣泛看到的template<typename T> classSp 控制代碼類實際上是android 為實現垃圾回收機制的智慧指標。智慧指標是c++ 中的一個概念,因為c++ 本身不具備垃圾回收機制,而且指標也不具備建構函式和解構函式,所以為了實現記憶體( 動態儲存區) 的安全回收,必須對指標進行一層封裝,而這個封裝就是智慧指標,其實說白了,智慧指標就是具備指標功能同時提供安全記憶體回收的一個類。當然,智慧指標的功能還不只這些,想了解更多大家可以去研究下~
智慧指標有很多實現方式,android 中的sp 控制代碼類實際上就是google實現的一種強引用的智慧指標。我沒有仔細看android sp 的實現方式,但其基本原理是固定的,現在我們從一個相對簡單的例子來看智慧指標的實現:
首先看一個最簡單的對指標的封裝:
Template <typename T>
class SmartPtr{
public:
SmartPtr(T *p = 0):ptr(p){}
~SmartPtr(){delete ptr ;}
private:
T *ptr ;
};
通過上面的封裝,我們就可以用下面的方式來使用SmartPtr 而不需要擔心記憶體洩露的問題:
SmartPtr<int> pointer(new int) ;
*(pointer.ptr) = 10 ;
為了方便使用,我們可以對操作符進行過載,讓智慧指標的操作更像是指標:
T &operator*(){return *ptr}
T* operator->(){return ptr}
經過上面的過載,我們就可以像使用真正的指標一樣而不需要去解決記憶體洩露問題。
因為智慧指標封裝了指標,所以必須為其實現拷貝建構函式和“=”操作符過載。因為如果不提供這兩個函式,當上面的智慧指標進行賦值的時候必然會使指標指向同一個區域,一旦析構的話會導致同一個指標被delete 兩次。
在這裡,拷貝建構函式的實現是有技巧 的,使用拷貝建構函式建立一個新的只能指標時,並不建立新的物件,而是讓新的智慧指標指向同一個物件,實際上就是常說的淺複製。但是這樣的話就會導致多個指標指向同一塊記憶體區域,當呼叫解構函式的時候如何來保證同一塊記憶體區域只會被delete 一次呢,這裡實現的方法有很多,最常用的是引數控制。即在智慧指標中加入一個計數器,每次增加一個對記憶體區域的強引用,則計數器加一,當計數器為0 時,這個物件就可以被刪除了, 這個計數器採用動態分配跟指標分開儲存, 因為這個計數器是多個智慧指標需要共享的:
Template <typename T>
class SmartPtr{
public:
SmartPtr(T *p = 0):ptr(p){count = new int(1) ;}// 第一次建立的時候,引數肯定是1
SmartPtr(const SmartPtr & rhs):ptr(rhs.ptr),count(rhs.count){++*rhs.count ;}
T &operator*(){return *ptr}
T* operator->(){return ptr}
SmartPtr &operator=(const SmartPtr & rhs){
if(ptr == rhs.ptr)
return *this ;
if(--*count == 0){
delete ptr ;
delete count ;
}
++*rhs.count ;
count = rhs.count ;
ptr = rhs.ptr ;
}
~SmartPtr(){
if(--*count==0)
delete ptr ;
delete count ;
}
private:
T *ptr ;
int *count ;
};
這樣,一個智慧指標就基本成形了,當然這只是最簡單的智慧指標,商業庫提供的智慧指標都提供非常強大的功能,如果能仔細研究透了android 在這方面的實現,應該在c++ 記憶體控制方面很有長進~~暫時還沒有時間,還要順著camera 往下看,有牛人懂的話email 多指教哈~~
出了智慧指標sp 外,android 裡面還出現了wp 這個指標類,實際上他是一個弱引用型別的指標類,弱引用是在.net 以及java 中經常用到的,弱引用是一個物件引用的持有者,使用弱引用後可以維持對物件的引用,但是不會阻止其被垃圾回收。如果一個物件只有弱引用了,那它就成為被垃圾回收的候選物件,就像沒有剩餘的引用一樣,而且一旦物件被刪除,所有的弱引用也會被清楚。弱引用適合那些資料成員特別多,而且重新建立又相對容易的類,也就是俗稱的胖子類,建立弱引用可以引用物件,但也不阻止其被垃圾回收,在記憶體的使用方面取得一定的平衡。
在android 中wp 類裡面的promote 函式實際上就是將一個弱引用升級為一個強引用。不管是sp 還是wp ,實際上都是android 利用現有的c++ 特性來解決記憶體使用和回收的一種手段。
Android手機作業系統既然是開源的作業系統。那麼在具體的資料夾中就會存放著各種相關功能的開原始碼。我們在使用的時候可以根據這些原始碼進行相應的修改就能輕鬆的完成我們所需的功能。在這裡大家就一起來看看Android智慧指標的相關原始碼解讀以及應用方法。
在Android的原始碼中,經常會看到形如:sp< xxx>、wp< xxx>這樣的型別定義,這其實是Android中的智慧指標。智慧指標是C++中的一個概念,通過基於引用計數的方法,解決物件的自動釋放的問題。在C++程式設計中,有兩個很讓人頭痛的問題:一是忘記釋放動態申請的物件從而造成記憶體洩露;二是物件在一個地方釋放後,又在別的地方被使用,從而引起記憶體訪問錯誤。
程式設計師往往需要花費很大精力進行精心設計,以避免這些問題的出現。在使用智慧指標後,動態申請的記憶體將會被自動釋放(有點類似Java的垃圾回收),不需要再使用delete來釋放物件,也不需要考慮一個物件是否已經在其它地方被釋放了,從而使程式編寫工作減輕不少,而程式的穩定性大大提高。
Android智慧指標相關的原始碼在下面兩個檔案中:
frameworks\base\include\utils\RefBase.h
frameworks\base\libs\utils\RefBase.cpp
Android中定義了兩種智慧指標型別,一種是強指標sp(strong pointer),一種是弱指標(weak pointer)。其實成為強引用和弱引用更合適一些。強指標與一般意義的智慧指標概念相同,通過引用計數來記錄有多少使用者在使用一個物件,如果所有使用者都放棄了對該物件的引用,則該物件將被自動銷燬。
弱指標也指向一個物件,但是弱指標僅僅記錄該物件的地址,不能通過弱指標來訪問該物件,也就是說不能通過弱智真來呼叫物件的成員函式或訪問物件的成員變數。要想訪問弱指標所指向的物件,需首先將弱指標升級為強指標(通過wp類所提供的promote()方法)。弱指標所指向的物件是有可能在其它地方被銷燬的,如果物件已經被銷燬,wp的promote()方法將返回空指標,這樣就能避免出現地址訪問錯的情況。
是不是很神奇?弱指標是怎麼做到這一點的呢?其實說穿了一點也不復雜,原因就在於每一個可以被智慧指標引用的物件都同時被附加了另外一個 weakref_impl型別的物件,這個物件中負責記錄物件的強指標引用計數和弱指標引用計數。這個物件是Android智慧指標的實現內部使用的,智慧指標的使用者看不到這個物件。弱指標操作的就是這個物件,只有當強引用計數和弱引用計數都為0時,這個物件才會被銷燬。
說了這麼多原理,下面該看看到底智慧指標該怎麼使用了。假設現在有一個類MyClass,如果要使用智慧指標來引用這個類的物件,那麼這個類需滿足下列兩個前提條件:
(1) 這個類是基類RefBase的子類或間接子類;
(2) 這個類必須定義虛建構函式,即它的建構函式需要這樣定義:
- virtual ~MyClass();
滿足了上述條件的類就可以定義Android智慧指標了,定義方法和普通指標類似。比如普通指標是這樣定義:
- MyClass* p_obj;
Android智慧指標是這樣定義:
- sp< MyClass> p_obj;
注意不要定義成 sp< MyClass>* p_obj。初學者容易犯這種錯誤,這樣實際上相當於定義了一個指標的指標。儘管在語法上沒有問題,但是最好永遠不要使用這樣的定義。
定義了一個智慧指標的變數,就可以象普通指標那樣使用它,包括賦值、訪問物件成員、作為函式的返回值、作為函式的引數等。比如:
- p_obj = new MyClass();
- // 注意不要寫成 p_obj = new sp< MyClass>
- sp< MyClass>p_objp_obj2 = p_obj;
- p_obj->func();
- p_obj = create_obj();
- some_func(p_obj);
注意不要試圖delete一個Android智慧指標,即 delete p_obj。不要擔心物件的銷燬問題,智慧指標的最大作用就是自動銷燬不再使用的物件。不需要再使用一個物件後,直接將指標賦值為NULL即可:
- p_obj = NULL;
上面說的都是強指標,弱指標的定義方法和強指標類似,但是不能通過弱指標來訪問物件的成員。下面是弱指標的示例:
- wp< MyClass>wp_obj = new MyClass();
- p_obj = wp_obj.promote();
- // 升級為強指標。不過這裡要用.而不是->,真是有負其指標之名啊
- wp_obj = NULL;
Android智慧指標用起來是很方便,在一般情況下最好使用智慧指標來代替普通指標。但是需要知道一個智慧指標其實是一個物件,而不是一個真正的指標,因此其執行效率是遠遠比不上普通指標的。所以在對執行效率敏感的地方,最好還是不要使用智慧指標為好。
原文四:
Android中通過引用計數來實現智慧指標,並且實現有強指標與弱指標。由物件本身來提供引用計數器,但是物件不會去維護引用計數器的值,而是由智慧指標來管理。
要達到所有物件都可用引用計數器實現智慧指標管理的目標,可以定義一個公共類,提供引用計數的方法,所有物件都去繼承這個公共類,這樣就可以實現所有物件都可以用引用計數來管理的目標,在Android中,這個公共類就是RefBase,同時還有一個簡單版本LightRefBase。
RefBase作為公共基類提供了引用計數的方法,但是並不去維護引用計數的值,而是由兩個智慧指標來進行管理:sp(Strong Pointer)和wp(Weak Pointer),代表強引用計數和弱引用計數。
一、輕量級引用計數的實現:LightRefBase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
template < class T>
class LightRefBase
{
public :
inline LightRefBase() : mCount(0) { }
inline void incStrong( const void *
id) const {
android_atomic_inc(&mCount);
}
inline void decStrong( const void *
id) const {
if (android_atomic_dec(&mCount) == 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 :
mutable volatile int32_t mCount;
};
|
LightRefBase的實現很簡單,只是內部儲存了一個變數用於儲存物件被引用的次數,並提供了兩個函式用於增加或減少引用計數。
二、sp(Strong Pointer)
LightRefBase僅僅提供了引用計數的方法,具體引用數應該怎麼管理,就要通過智慧指標類來管理了,每當有一個智慧指標指向物件時,物件的引用計數要加1,當一個智慧指標取消指向物件時,物件的引用計數要減1,在C++中,當一個物件生成和銷燬時會自動呼叫(拷貝)建構函式和解構函式,所以,對物件引用數的管理就可以放到智慧指標的(拷貝)建構函式和解構函式中。Android提供了一個智慧指標可以配合LightRefBase使用:sp,sp的定義如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
template < typename T>
class sp
{
public :
inline 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);
~sp();
// Assignment
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);
//! Special optimization for use by ProcessState (and nobody else).
void force_set(T* other);
// Reset
void clear();
// Accessors
inline T& operator* () const { return *m_ptr;
}
inline T* operator-> () const { return m_ptr;
}
inline T* get() const { return m_ptr;
}
// Operators
COMPARE(==)
COMPARE(!=)
COMPARE(>)
COMPARE(<)
COMPARE(<=)
COMPARE(>=)
private :
template < typename Y> friend class sp;
template < typename Y> friend class wp;
void set_pointer(T* ptr);
T* m_ptr;
};
|
程式碼比較多,其中Accessors部分程式碼過載了*、->操作符使我們使用sp的時候就像使用真實的物件指標一樣,可以直接操作物件的屬性或方法,COMPARE是巨集定義,用於過載關係操作符,由於對引用計數的控制主要是由(拷貝)建構函式和解構函式控制,所以忽略其他相關程式碼後,sp可以精簡為如下形式(賦值操作符也省略掉了,建構函式省略相似的兩個):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
template < typename T>
class sp
{
public :
inline sp() : m_ptr(0) { }
sp(T* other);
sp( const sp<T>&
other);
~sp();
private :
template < typename Y> friend class sp;
template < typename Y> friend class wp;
void set_pointer(T* ptr);
T* m_ptr;
};
|
預設建構函式使智慧指標不指向任何物件,sp(T* other)與sp(
const
sp<T>& other)
的實現如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
template < typename T>
sp<T>::sp(T* other)
: m_ptr(other)
{
if (other) other->incStrong( this );
}
template < typename T>
sp<T>::sp( const sp<T>&
other)
: m_ptr(other.m_ptr)
{
if (m_ptr) m_ptr->incStrong( this );
}
|
內部變數m_ptr指向實際物件,並呼叫實際物件的incStrong函式,T繼承自LightRefBase,所以此處呼叫的是LightRefBase的incStrong函式,之後實際物件的引用計數加1。
當智慧指標銷燬的時候呼叫智慧指標的解構函式:
1 2 3 4 5 |
template < typename T>
sp<T>::~sp()
{
if (m_ptr) m_ptr->decStrong( this );
}
|
呼叫實際物件即LightRefBase的decStrong函式,其實現如下:
1 2 3 4 5 |
inline void decStrong( const void *
id) const {
if (android_atomic_dec(&mCount) == 1) {
delete static_cast < const T*>( this );
}
}
|
android_atomic_dec返回mCount減1之前的值,如果返回1表示這次減過之後引用計數就是0了,就把物件delete掉。
三、RefBase
RefBase提供了更強大的引用計數的管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
class RefBase
{
public :
void incStrong( const void *
id) const ;
void
|