說到智能指針吱涉,網上相關資料數不勝數,這里我就我自己的理解給大家分享一下猿诸。
1.什么是智能指針
我們在編寫c++程序的時候都知道所使用的對象都有著嚴格定義的生命周期:
? ? ? 全局對象在程序啟動時分配內存辅甥,在程序結束時銷毀;
? ? ? 局部對象在進入其定義所在的程序塊時被創(chuàng)建祟绊,在離開塊時被銷毀楼入;
? ? ? 局部static對象在第一次使用前分配,在程序結束時銷毀久免;
? ? ? 動態(tài)對象只有當顯示的被釋放時浅辙,方能被銷毀扭弧;
動態(tài)對象的正確釋放被證明是編程中極其容易出錯的地方:
由于WebKit大量使用動態(tài)對象遵馆,所以類似這樣的錯誤肯定會有很多,為了更安全的使用動態(tài)對象丰榴,WebKit使用智能指針來管理動態(tài)內存货邓,當一個對象應該被釋放時,指向它的智能指針可以確保自動地釋放它四濒。
2.智能指針實現原理
智能指針(smart pointer)的一種通用實現技術是使用引用計數(reference count)换况;
智能指針類將一個計數器與類指向的對象相關聯(lián),引用計數跟蹤該類有多少個對象共享同一指針盗蟆;
每次創(chuàng)建類的新對象時戈二,初始化指針并將引用計數置為1;
當對象作為另一對象的副本而創(chuàng)建時喳资,拷貝構造函數拷貝指針并增加與之相應的引用計數觉吭;
對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數骨饿,并增加右操作數所指對象的引用計數亏栈;
調用析構函數時台腥,構造函數減少引用計數
如果引用計數減至0,則刪除基礎對象
3.Webkit智能指針
官方文檔(2015.4.27):
http://www.webkit.org/coding/RefPtr.html
WebKit智能指針歷史:
最早绒北,很多對象采用引用計數(繼承自模板類RefCounted)黎侈,依靠手動調用其ref()或者deref()的方式來實現
到了2005年,發(fā)現越來越多的內存泄露問題闷游,其原因就是ref和deref函數調用不匹配導致峻汉。于是WebKit采用智能指針來解決此類問題
但是早期的試驗表明智能指針會進行額外的引用計數處理而影響性能。因此我們尋找一種方式來讓我們使用智能指針同時避免引用計數跳變(churn)
Maciej Stachowiak設計了一組類模板脐往,RefPtr和PassRefPtr休吠,實現這一模式來解決WebKit中惱人的引用計數問題;而C++11標準中增加的std::move操作可以更高效的來解決业簿,所以PassRefPtr逐漸被廢棄
到了2013年瘤礁,發(fā)現針對智能指針和原始指針的使用過程中,判空操作激增梅尤,但其實很多都是沒有必要的柜思;WebKit開始更多的使用引用(Ref)來代替指針(RefPtr)
WebKit智能指針實現:
WebKit智能指針由類族RefPtr來實現,其核心有如下三個類:RefCounted巷燥、RefPtr赡盘、Ref
其中RefCounted提供了引用計數器,RefPtr缰揪、Ref提供了自動管理引用計數器的功能
RefCounted源碼在RefCounted.h中陨享,這個文件里定義了兩個類:非模板類RefCountedBase和模板類RefCounted
定義了成員變量:int m_refCount
前面的ref()和deref()就是RefCounted的核心功能了,ref時引用計數加1钝腺,deref時引用計數減1抛姑,減到0就將自己銷毀
使用時需要繼承自RefCounted,但是這里不同于一般的繼承拍屑,舉例:
但是僅僅使用RefCounted類還無法稱之為智能指針途戒,RefCounted使用方法繁瑣
為了簡化RefCounted的使用方法,RefPtr誕生了僵驰,其源代碼在RefPtr.h中喷斋,在這個文件中定義了一個模板類RefPtr,RefPtr才能算一個簡單的智能指針
看上面的代碼就能很清楚的知道蒜茴,當把一個對象賦值給RefPtr包裝過的對象后星爪,它會先被賦值的對象ref,然后再給自己原來的對象deref粉私,這實際上就是上例中setTitle的過程顽腾,所以改寫后就極大簡潔了代碼
這樣修改雖然簡潔了代碼,但沒有簡潔代碼實際的執(zhí)行過程。此段代碼還是會頻繁的使用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()
同樣砚嘴,可以通過智能引用Ref的get函數獲取到原始引用,也可以通過智能引用Ref的ptr函數獲取到原始指針
leafRef()
adoptRef()
? ? ? 一個繼承自RefCounted模板類的對象在被創(chuàng)建時际长,需要保證引用計數值為1。最好的辦法是將創(chuàng)建的對象放到Ref中兴泥,以免操作完成后忘記調用此對象的deref()函數工育,這意味著只要調用此類的new操作后要立即調用adoptRef函數
在WebKit中創(chuàng)建對象時,采用create函數替代直接new操作
4.WebKit智能指針使用原則
針對局部變量:
? ? ? 如果生命周期和所有者是確認的搓彻,則允許使用原始引用或指針
? ? ? 如果代碼需要維持對該對象的引用并確定它的生命周期如绸,則應該使用Ref,如果其值可能為null旭贬,則使用RefPtr
針對類的成員變量:
? ? ? 如果生命周期和所有者是確認的怔接,則允許使用原始引用或指針
? ? ? 如果這個類需要維持對該對象的引用并確定它的生命周期,則應該使用Ref或者RefPtr
針對函數的形參:
? ? ? 如果該函數不需要維持對該對象的引用稀轨,則函數參數允許使用原始引用或指針
? ? ? 如果該函數需要維持對該對象的引用扼脐,則函數參數應該是Ref&&或者RefPtr&&。這種情況常見于很多setter函數
針對函數的返回值:
? ? ? 如果返回值是一個對象奋刽,并且所有者并未轉移瓦侮,則返回值類型應該是原始引用或者指針艰赞。這種情況常見于很多getter函數
? ? ? 如果返回值是一個新創(chuàng)建的對象,或者其所有者因為某些原因需要被轉移肚吏,則返回值類型應該是Ref或者RefPtr
新對象:
? ? ? 一個新創(chuàng)建出來的對象應該立即轉化為Ref引用猖毫,以便智能指針能夠自動的對所有引用計數
? ? ? 針對繼承自RefCounted類的對象,上面的過程需要用adoptRef函數來轉化
? ? ? 好的使用智能指針的習慣是將類的構造函數定義為私有函數须喂,并且定義一個公共的create函數用來創(chuàng)建類的對象并返回一個Ref引用