Refptr and PassRefPtr basic

http://www.webkit.org/coding/RefPtr.html

Refptr and PassRefPtr basic

Webkit中的很多對象都是引用計數的,所使用的方法是在類中加入ref和deref成員函數用於增加和減小引用計數。調用ref的次數一定要和調用deref的次數相同,當引用計數deref到1的時候,該對象會被釋放。Webkit中的很多類通過繼承RefCounted類模板來實現這種方法。

2005年,我們發現有很多的內存泄露,特別是在HTML editing code中誤用了ref和deref調用的時候。

我們想用智能指針來解決這個問題。但是,一些驗證性試驗表明智能指針會使用額外的引用計數操作,會導致性能降低。例如,如果一個函數使用了智能指針作爲參數,並且返回同樣的智能指針作爲返回值,單就是作爲參數進行傳遞和返回該值,由於對象在智能指針之間傳遞,就會導致增加或者減少引用計數兩到四次。所以我們在尋找一種理想的方式,能讓我們使用智能指針,並且避免大量使用引用計數。

一個解決方法的靈感來自於C++標準類模板auto_ptr。使用auto_ptr的對象之間在賦值的時候,它的所有者也發生了改變,賦值者的引用數會變爲0,對象歸被賦值者所有。

Maciej Stachowiak設計了一對類模板,RefPtr和PassRefPtr,實現了這種方案,來解決WebCore的引用計數問題。

原始指針(Rawpointer)

當討論類似RefPtr類模板這樣的智能指針的時候,我們使用了原始指針這個術語來表示C++語言的內建指針類型。這裏給出了使用原始指針實現的規範的setter函數:

// example, not preferred style

 

class Document {

   ...

   Title* m_title;

}

 

Document::Document()

    :m_title(0)

{

}

 

Document::~Document()

{

   if (m_title)

       m_title->deref();

}

 

void Document::setTitle(Title* title)

{

   if (title)

       title->ref();

   if (m_title)

       m_title->deref();

   m_title = title;

}

RefPtr

RefPtr是一個簡單的智能指針類,它調用ref來增加引用,調用deref來減小引用。RefPtr在任何有ref和deref成員函數的對象上都會起作用,下面是用RefPtr寫的setter例子

// example, not preferred style

 

class Document {

   ...

   RefPtr<Title> m_title;

}

 

void Document::setTitle(Title* title)

{

   m_title = title;

}

如果單獨使用RefPtr也會導致引用計數的泛濫

RefPtr<Node> createSpecialNode()

{

   RefPtr<Node> a = new Node;

   a->setSpecial(true);

   return a;

}

 

RefPtr<Node> b = createSpecialNode();

我們假設node對象的引用計數從0開始,當它賦值給a的時候,引用計數增加了1(引用operator=被RefPtr重載了),從函數返回的時候,因爲a實際上只是一個臨時變量,編譯器會把它複製給一個臨時變量作爲返回值,此時發生了賦值操作,導致引用計數變爲2,同時由於a是臨時變量,所以在a被銷燬的時候,析構函數被調用,導致引用計數變爲1。作爲返回值的臨時變量把值賦值給變量b,此時引用計數又會增加爲2,在臨時變量銷燬的時候,引用計數又變爲1。

(如果編譯器進行了返回值優化的話,會減少一次引用計數的增加減少過程)

如果函數的參數和返回值都涉及了智能指針的使用,那麼引用計數導致的overhead會更多。

解決方案就是PassRefPtr

PassRefPtr

PassRefPtr與RefPtr類似,但是有一點不同,當你複製一個PassRefPtr或者把一個PassRefPtr賦值給一個RefPtr或者另外一個PassRefPtr的時候,PassRefPtr中的原始指針會被設置爲0;該操作不會導致引用計數有任何的變化。下面是用PassRefPtr的例子

PassRefPtr<Node> createSpecialNode()

{

   PassRefPtr<Node> a = new Node;

   a->setSpecial(true);

   return a;

}

 

RefPtr<Node> b = createSpecialNode();

假設Node對象起始引用計數爲0,當它賦值給a的時候,引用計數增加爲1。在函數返回的時候,變量a賦值給返回結果臨時變量的時候,a中的引用被設置爲0,在返回結果臨時變量賦值給變量b的時候,b中的引用被設置爲0。

注:PassRefPtr通過在賦值的時候,傳遞指針而不是增加減少引用計數的方式,降低了引用計數氾濫導致的性能降低問題。

但是,Safari team在實踐中發現,PassRefPtr這種方式很容易出現問題。

static RefPtr<Ring>g_oneRingToRuleThemAll;

 

void finish(PassRefPtr<Ring> ring)

{

   g_oneRingToRuleThemAll = ring;

   ...

   ring->wear();

}

在ring->wear()的時候,ring中包含的指針已經被設置爲0.爲了避免發生這種情況,我們建議只在函數參數的返回值的時候,使用PassRefPtr,如果要使用其中的變量,要把它賦值給一個RefPtr本地變量

static RefPtr<Ring>g_oneRingToRuleThemAll;

 

void finish(PassRefPtr<Ring> prpRing)

{

   RefPtr<Ring> ring = prpRing;

   g_oneRingToRuleThemAll = ring;

    ...

   ring->wear();

}

混合使用RefPtr和PassRefPtr

我們建議,在作爲參數進行傳遞和函數返回值的情況下使用PassRefPtr,其他情況下使用RefPtr,但是也有很多時候,你會希望像PassRefPtr那樣,轉移RefPtr的所有權。RefPtr有一個名爲release的成員函數實現着這功能。它設置RefPtr的值爲0,並且構建新的PassRefPtr而不改變引用計數。

PassRefPtr<Node> createSpecialNode()

{

   RefPtr<Node> a = new Node;

   a->setCreated(true);

   return a.release();//這裏返回PassRefPtr,並且把a中的引用設置爲0

}

 

RefPtr<Node> b = createSpecialNode();

這既保證了PassRefPtr的效率,又降低了技巧性的語義導致問題的機會。

 

與原始指針一起使用

如果一個函數的參數需要使用原始指針,那麼可以使用RefPtr的get函數

printNode(stderr, a.get());

但是很多操作可以直接使用RefPtr或者PassRefPtr,而不用顯式的調用get函數

RefPtr<Node> a = createSpecialNode();

Node* b = getOrdinaryNode();

 

// the * operator

*a = value;

 

// the -> operator

a->clear();

 

// null check in an if statement

if (a)

   log("not empty");

 

// the ! operator

if (!a)

   log("empty");

 

// the == and != operators, mixing with rawpointers

if (a == b)

   log("equal");

if (a != b)

   log("not equal");

 

// some type casts

RefPtr<DerivedNode> d =static_pointer_cast<DerivedNode>(a);

注:RefPtr對多種操作符都進行了重載

一般來說,RefPtr和PassRefPtr踐行了一個簡單的規則:他們總是平衡ref和deref調用,來保證程序員不會丟失對deref的調用。但是對於我們已經有了一個原始指針,並且有了引用計數,希望能夠轉移它的所有權的情況,應該使用adoptRef函數。

RefPtr<Node> node =adoptRef(rawNodePointer);

反過來,如果想把RefPtr傳遞給一個原始指針,而不改變引用計數,那麼就要使用PassRefPtr的leakRef函數

RefPtr和new objects

在這裏的例子中,我們談論的對象都是引用計數爲0,但是爲了效率和簡化,RefCounted類根本不使用引用計數0。對象在創建的時候就使用引用計數1.最好的編程實踐是在new以後,馬上把它們放進RefPtr,這樣可以防止程序員在用完這些對象的時候忘記調用deref,讓RefPtr幫助我們管理這些對象。也就是說,任何人調用new創建一個對象後,要馬上調用adoptRef(該函數會返回一個PassRefPtr)。在WebCore中我們使用命名爲create的static函數,而不是直接使用new

PassRefPtr<Node> Node::create()

{

   return adoptRef(new Node);

}

 

RefPtr<Node> e = Node::create();

從adoptRef和PassRefPtr的實現,我們可以看到它們是很高效的。對象的起始引用計數是1,而且在創建過程中沒有任何對引用計數的操作

// preferred style

PassRefPtr<Node> createSpecialNode()

{

   RefPtr<Node> a = Node::create();

   a->setCreated(true);

   return a.release();

}

 

RefPtr<Node> b = createSpecialNode();

Node對象在Node::create中,被adoptRef放進PassRefPtr中,然後通過RefPtr的重載過的=操作符賦值給RefPtr對象a,最後在函數返回的時候,RefPtr的release函數返回PassRefPtr對象,然後又通過賦值操作賦值給RefPtr對象b,這期間沒有對引用計數發生過任何操作。

RefCounted類實現了一個運行期的檢查,如果我們再創建一個對象之後,沒有調用adoptRef,而調用ref或者deref將會發生assertion。

使用指南

在WebKit代碼中使用RefPtr和PassRefPtr應遵守如下的使用指南:

本地變量

如果所有權和生命期可以保證是在局部的,那麼本地變量可以使用原始指針

如果代碼需要保持住所有權或者保證生命期,那麼應該使用RefPtr本地變量

永遠不要使用PassRefPtr作爲本地變量

成員變量

如果能夠保障成員變量的所有權和生命期,成員變量可以使用原始指針

如果類需要獲得所有權或者保證生命期,那麼成員變量應該使用RefPtr

成員變量永遠不要使用PassRefPtr

函數參數

如果函數不用於獲取對象的所有權,那麼參數可以使用原始指針

如果函數需要獲取對象的所有權,那麼參數應該是PassRefPtr。大部分的setter函數都應該如此設計。除非參數非常簡單,否則的話,參數應該在函數一開始就傳遞給RefPtr對象,通常函數參數名稱可以以”prp”作爲前綴

函數結果

如果函數結果是一個對象,但是該對象的所有權還是屬於原來對象,那麼這個返回對象應該是一個原始指針,大部分的getter函數都是如此定義的(注:即結果對象是包含在原始對象中的,通過getter只是返回其引用或者指針,但是該返回的對象仍然從屬於原始對象)

如果函數結果是一個新生成的對象或者它的所有權因爲某種原因需要轉移,那麼這個結果應該使用PassRefPtr(該對象可以轉移所有權)。因爲本地變量一般使用RefPtr,所以在函數返回的時候,應該調用RefPtr的release函數返回一個PassRefPtr對象

新對象

新創建的對象在創建後需要馬上放入到RefPtr中,這樣只能指針就是自動控制引用計數操作

對於RefCounted對象,應該使用adoptRef函數把它放入到PassRefPtr中。

注:WebCore中的例子

static PassRefPtr<DocumentLoader> DocumentLoader::create(constResourceRequest& request, const SubstituteData& data)

{

returnadoptRef(new DocumentLoader(request, data));

}

DocumentLoader繼承自RefCounted<DocumentLoader>

 

最好的創建方式是,使用私有的構造函數,和共有的創建函數,該創建函數返回一個PassRefPtr,(注:採用私有的構造函數,就防止外界使用new來創建新的對象,必須使用類提供的靜態創建函數,該函數保證了對象在被創建後馬上就被放入到RefPtr中)

 

這篇文章是關於Webkit中一系列非常基礎的類的說明,看明白這篇文檔之前還需要了解如下信息:

RefCounted是所有智能指針的基類,它提供了ref和deref操作,用於增減引用計數,RefCounted還有它自己的基類,RefCountedBase。

爲了解決引用計數在作爲參數和返回值的時候,頻繁增減的問題,引入RefPtr和PassRefPtr對象,和一系列使用這些對象的規則

首先需要說明的是RefPtr和PassRefPtr是模板,使用這些模板定義的對象同樣也一個對象而不是指針,在這些對象中封裝了對引用計數的ref和deref操作。

其次來看一些重要的方法實現,對於我們理解上述文檔很有好處,

RefPtr::release(),該函數返回一個PassRefPtr對象,用於傳遞原始指針的所有權

PassRefPtr<T> RefPtr::release()

{

 PassRefPtr<T> tmp = adoptRef(m_ptr);m_ptr = 0; return tmp;

}

PassRefPtr<T>::leakRef(),該函數返回原始指針

template<typename T> inline T*PassRefPtr<T>::leakRef() const

    {

       T* ptr = m_ptr;

       m_ptr = 0;

       return ptr;

}

adoptRef這個函數非常重要,它返回一個PassRefPtr對象,經常在創建對象的函數中使用。

template<typename T> inlinePassRefPtr<T> adoptRef(T* p)

    {

       adopted(p);

       return PassRefPtr<T>(p, true);

}

同時值得說明的是,在RefPtr和PassRefPtr對象中對引用計數的操作,統一調用了

void refIfNotNull(T*ptr)和void derefIfNotNull(T* ptr),在PassRefPtr中對這兩個函數的實現如下:

template<typename T> REF_DEREF_INLINEvoid refIfNotNull(T* ptr)

    {

       if (LIKELY(ptr != 0))

           ptr->ref();

    }

 

   template<typename T> REF_DEREF_INLINE void derefIfNotNull(T* ptr)

    {

       if (LIKELY(ptr != 0))

           ptr->deref();

}

在RefCountedBase中實現了ref和deref方法,從而實現了智能指針對其保存的引用計數的操作。

而RefPtr和PassRefPtr智能指針並不是只能用於RefCountedBase類對象的,只要重現實現void refIfNotNull(T* ptr)和void derefIfNotNull(T* ptr)函數,就能將智能指針用於其他有引用計數的對象或者結構,在WebCore中,RefPtrCairo.h中就定義了一系列這樣的函數,這樣在包含該頭文件以後,我們就可以使用RefPtr和PassRefPtr管理Cairo的對象了

 

智能指針:

http://baike.baidu.com/view/1391603.htm

http://zh.wikipedia.org/wiki/%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章