第三章 WebKit智能指針詳解

說到智能指針,網上相關資料數不勝數,這里我就我自己的理解給大家分享一下。

1.什么是智能指針

我們在編寫c++程序的時候都知道所使用的對象都有著嚴格定義的生命周期:

? ? ? 全局對象在程序啟動時分配內存,在程序結束時銷毀;

? ? ? 局部對象在進入其定義所在的程序塊時被創建,在離開塊時被銷毀;

? ? ? 局部static對象在第一次使用前分配,在程序結束時銷毀;

? ? ? 動態對象只有當顯示的被釋放時,方能被銷毀;

動態對象的正確釋放被證明是編程中極其容易出錯的地方:

常見錯誤一:忘記delete,導致內存泄露;
常見錯誤二:野指針,對象已經被釋放,這里注意,此時的指針成為了懸垂指針,即指向曾經存在的對象,但該對象已經不再存在。結果未定義,而且難以檢測)這時候我們再次使用,會產生使用非法內存的指針;
常見錯誤三:重復delete;

由于WebKit大量使用動態對象,所以類似這樣的錯誤肯定會有很多,為了更安全的使用動態對象,WebKit使用智能指針來管理動態內存,當一個對象應該被釋放時,指向它的智能指針可以確保自動地釋放它。


2.智能指針實現原理

智能指針(smart pointer)的一種通用實現技術是使用引用計數(reference count);

智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針;

每次創建類的新對象時,初始化指針并將引用計數置為1;

當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針并增加與之相應的引用計數;

對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數,并增加右操作數所指對象的引用計數;

調用析構函數時,構造函數減少引用計數

如果引用計數減至0,則刪除基礎對象


3.Webkit智能指針

官方文檔(2015.4.27):

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

WebKit智能指針歷史:

最早,很多對象采用引用計數(繼承自模板類RefCounted),依靠手動調用其ref()或者deref()的方式來實現

到了2005年,發現越來越多的內存泄露問題,其原因就是ref和deref函數調用不匹配導致。于是WebKit采用智能指針來解決此類問題

但是早期的試驗表明智能指針會進行額外的引用計數處理而影響性能。因此我們尋找一種方式來讓我們使用智能指針同時避免引用計數跳變(churn)

Maciej Stachowiak設計了一組類模板,RefPtr和PassRefPtr,實現這一模式來解決WebKit中惱人的引用計數問題;而C++11標準中增加的std::move操作可以更高效的來解決,所以PassRefPtr逐漸被廢棄

到了2013年,發現針對智能指針和原始指針的使用過程中,判空操作激增,但其實很多都是沒有必要的;WebKit開始更多的使用引用(Ref)來代替指針(RefPtr)

WebKit智能指針實現:

WebKit智能指針由類族RefPtr來實現,其核心有如下三個類:RefCounted、RefPtr、Ref

其中RefCounted提供了引用計數器,RefPtr、Ref提供了自動管理引用計數器的功能

RefCounted源碼在RefCounted.h中,這個文件里定義了兩個類:非模板類RefCountedBase和模板類RefCounted

定義了成員變量:int m_refCount

函數:ref()
函數:deref()
函數:derefBase()

前面的ref()和deref()就是RefCounted的核心功能了,ref時引用計數加1,deref時引用計數減1,減到0就將自己銷毀

使用時需要繼承自RefCounted,但是這里不同于一般的繼承,舉例:

但是僅僅使用RefCounted類還無法稱之為智能指針,RefCounted使用方法繁瑣

每次操作對象都要做ref()或者deref()操作

為了簡化RefCounted的使用方法,RefPtr誕生了,其源代碼在RefPtr.h中,在這個文件中定義了一個模板類RefPtr,RefPtr才能算一個簡單的智能指針

看上面的代碼就能很清楚的知道,當把一個對象賦值給RefPtr包裝過的對象后,它會先被賦值的對象ref,然后再給自己原來的對象deref,這實際上就是上例中setTitle的過程,所以改寫后就極大簡潔了代碼

修改版

這樣修改雖然簡潔了代碼,但沒有簡潔代碼實際的執行過程。此段代碼還是會頻繁的使用ref和deref,這樣就會導致引用計數跳變的問題,比如:

開始引用計數為1

setTitle將untitledTitle賦值給document的成員變量,引用計數增加為2

此程序塊返回,untitledTitle變量銷毀,引用計數減少到1

引用計數跳變在函數參數和返回值都涉及的情況下更嚴重

終極解決方案:

在本例中,引用計數始終為1,另外,WTF::move主要是封裝了std::move(此函數在賦值操作中直接取右值,中間不會涉及到引用計數加減),并添加了錯誤處理。另外還可以結合PassRefPtr處理這種情況,具體百度有相關資料,RefPtr和PassRefPtr,講得還是非常容易理解的。

Ref源代碼在Ref.h中,在這個文件中定義了一個模板類Ref,Ref很像RefPtr,但是Ref是一個引用,而RefPtr是一個指針,Ref是一個智能引用,所以其值不可能為null

智能指針相關函數功能:

get()

可以通過智能指針RefPtr的get函數獲取到原始指針

同樣,可以通過智能引用Ref的get函數獲取到原始引用,也可以通過智能引用Ref的ptr函數獲取到原始指針

leafRef()

此函數把管理的指針轉移給接受者,不涉及到引用計數的操作

adoptRef()

原始指針轉換為智能指針

? ? ? 一個繼承自RefCounted模板類的對象在被創建時,需要保證引用計數值為1。最好的辦法是將創建的對象放到Ref中,以免操作完成后忘記調用此對象的deref()函數,這意味著只要調用此類的new操作后要立即調用adoptRef函數

在WebKit中創建對象時,采用create函數替代直接new操作


4.WebKit智能指針使用原則

針對局部變量:

? ? ? 如果生命周期和所有者是確認的,則允許使用原始引用或指針

? ? ? 如果代碼需要維持對該對象的引用并確定它的生命周期,則應該使用Ref,如果其值可能為null,則使用RefPtr

針對類的成員變量:

? ? ? 如果生命周期和所有者是確認的,則允許使用原始引用或指針

? ? ? 如果這個類需要維持對該對象的引用并確定它的生命周期,則應該使用Ref或者RefPtr

針對函數的形參:

? ? ? 如果該函數不需要維持對該對象的引用,則函數參數允許使用原始引用或指針

? ? ? 如果該函數需要維持對該對象的引用,則函數參數應該是Ref&&或者RefPtr&&。這種情況常見于很多setter函數

針對函數的返回值:

? ? ? 如果返回值是一個對象,并且所有者并未轉移,則返回值類型應該是原始引用或者指針。這種情況常見于很多getter函數

? ? ? 如果返回值是一個新創建的對象,或者其所有者因為某些原因需要被轉移,則返回值類型應該是Ref或者RefPtr

新對象:

? ? ? 一個新創建出來的對象應該立即轉化為Ref引用,以便智能指針能夠自動的對所有引用計數

? ? ? 針對繼承自RefCounted類的對象,上面的過程需要用adoptRef函數來轉化

? ? ? 好的使用智能指針的習慣是將類的構造函數定義為私有函數,并且定義一個公共的create函數用來創建類的對象并返回一個Ref引用


PassRefPtr、RefPtr與Raw Ptr轉換圖
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,541評論 1 51
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,581評論 25 708
  • 引言:由于未來需要深入android底層進行系統級別的開發,所以最近在看老羅的《Android系統源代碼情景分析》...
    拿破輪閱讀 2,239評論 0 9
  • 指針 在傳統的C++編程中,指針的使用一直是一把雙刃劍。指針賦予了我們直接操作硬件地址的能力,但同時也帶來了諸多問...
    passerbywhu閱讀 2,914評論 0 2
  • 前天下午偶然翻閱手機,微信服務通知里顯示“微信邀請你使用公眾號原創保護功能”。記得我發第一篇公眾號文章的時候,就有...
    桃小妖聲聲慢閱讀 381評論 0 0