快速導航php
1、 回顧歷史
2、 智能指針簡介
3、 Delphi中的interface
4、 Delphi中智能指針的實現
5、 interface + 泛型 = 強類型的智能指針!
6、 智能指針與集合
7、 注意事項
8、 總結 html
本隨筆全部源代碼打包下載 c++
在c++中,對象能夠建立在棧裏,也能夠建立在堆裏。如:程序員
// 如下代碼建立棧對象ide
棧對象生命週期由後臺管理。當方法結束時,棧對象會從棧中彈出,編譯器會自動銷燬棧所彈出的對象。函數
// 如下代碼建立堆對象post
堆對象保存在堆中,堆對象生命週期不受後臺管理,程序員必須本身手動的釋放堆對象,不然會形成內存泄露:性能
Pascal語言從OOP Pascal開始支持面向對象,也就是說,OOP Pascal支持建立對象了。OOP Pascal和c++同樣,也能夠分別建立棧對象和堆對象:測試
咱們最多見的OOP Pascal堆對象的定義和建立:spa
運行結果:
OOP Pascal也有棧對象,棧對象的定義和建立:
運行結果:
從結果咱們能夠看到,與c++不一樣的是,OOP Pascal所謂的棧對象的構造和析構,不受constructor方法和destructor方法控制,咱們不能捕獲到OOP Pascal棧對象的構造和析構。
通過前面分析,咱們知道,棧對象的聲明週期由後臺管理,棧對象在聲明時進行構造,當方法退出或者類被銷燬時(此時棧對象爲類的成員變量),棧對象的生命週期也會隨着結束,後臺自動會調用它們的析構函數並釋放棧空間。
而堆對象必須由程序員手動的釋放,若是一個方法只有一兩個堆對象咱們還能應付的過來,可是當堆對象很是多,並且堆對象通常都要通過多個方法的傳遞、賦值,傳遞到最後,很是容易忘了delete,形成內存泄露。
能不能讓後臺也去自動管理堆對象的釋放呢?前輩們想到一個辦法,就是讓一個棧對象包含一個堆對象的引用,當棧對象被後臺自動釋放時,會調用棧對象的析構函數,因而,在棧對象的析構函數裏寫下delete堆對象指針的語句。這樣,就完成了後臺間接管理堆對象,以上就是stl中的智能指針auto_ptr的處理方法。
從智能指針的簡介中咱們能夠了解到,要使用智能指針,咱們必須得捕獲到棧對象的構造函數,將堆對象的指針傳入棧對象,由棧對象保存堆對象的指針;還必須捕獲到棧對象的析構函數,在棧對象的析構函數裏進行對構造函數所傳入堆對象指針delete。在c++很容易作到這一點,可是經上面分析,咱們沒法對Delphi的棧對象進行構造和析構的捕獲。
咱們能夠換一種角度思考,不必定非要是棧對象,只要在Delphi中能有一種東西,只要出了它的做用域,它就能自動析構!
Delphi中的interface能間接知足咱們這個須要,請看如下例子:
有結果能夠看到,代碼中沒有釋放testInter指向的對象,對象由後臺釋放了。若是將1*處改成testInter: TTestInterface;則結果以下,咱們將看到若是不聲明爲接口,即便建立同一個對象,Delphi是不會自動釋放對象的。
在此,咱們利用了接口的自動管理功能,它本身維護着一個引用計數,當引用計數爲0時接口本身會調用析構函數。關於Delphi接口的一些概念以及爲何後臺會自動釋放接口,能夠參考如下兩篇文章,在此不作多餘敘述。
一、 Delphi 的接口機制淺探http://www.d99net.net/article.asp?id=206
二、 淺談引用計數http://www.moon-soft.com/doc/13056.htm
有了以上經驗,咱們就能夠實現咱們的智能指針了!
首先,咱們要建立一個繼承於TInterfacedObject的對象,在構造函數中傳入要管理的堆對象的引用,在析構函數裏FreeAndNil這個堆對象的引用。代碼以下:
而後咱們寫一個控制檯程序作試驗:
代碼執行結果以下圖所示:
若是咱們將代碼2*處替換成
TClassicalAutoPtr.Create (tt);
執行結果將看不到Destroy,析構函數沒有被調用。由於由TClassicalAutoPtr.New返回的是一個interface,而TClassicalAutoPtr.Create返回的是一個Object。
這樣,咱們一個簡單的智能指針就完成了。
D2009引入了泛型,咱們把程序稍微改動一下,就能夠支持強類型的智能指針了!
關於D2009對泛型的支持的分析,請參看我另外兩篇隨筆:
http://www.cnblogs.com/felixYeou/archive/2008/08/22/1273989.html
http://www.cnblogs.com/felixYeou/archive/2008/08/22/1274202.html
咱們以stl的auto_ptr做爲參照物,要是我們的智能指針看起來「優雅」,必須還要實現如下幾個方法:
一、 Get:返回智能指針所指向的對象
二、 Release:釋放智能指對堆對象的管理,智能指針被自動釋放後,不對堆對象進行釋放
三、 Reset:爲智能指針指向其它堆對象,同時釋放原來指向的堆對象
對於auto_ptr一些運算符重載,這裏不考慮在內,由於Delphi2009尚未支持類的運算符重載。
話很少說了,直接上代碼:
智能指針類代碼:
測試代碼:
測試結果爲:
然而咱們將3*處代碼改爲
ap.Release.DoPrintInt,則輸出結果爲
由於Release方法已經通知智能指針無論理堆對象了。
同時,咱們還能夠把DoTestAutoPtr方法寫成這樣,或許這樣建立TTestClass對象更優美一些:
tt.DoPrintInt;
// 不須要使用tt.Free;
若是咱們聲明一個全局變量:
並從DoTestAutoPtr方法開始改變其下代碼:
結果以下:
咱們能夠看到,當調用完畢DoTestAutoPtr方法後,方法內的堆對象tt並無銷燬,這說明智能指針ap並無銷燬。
由於在DoTestAutoPtr方法最後一行,將ap接口變量賦值給了全局變量gAp,此時接口的引用計數+1,方法退出後,ap變量被銷燬,接口的引用計數-1,可是gAp仍然引用着對象,因此引用計數不爲0。當運行到第4*步時,強制把gAp指向空地址,對象的引用計數-1,爲0,這個時候後臺自動調用對象的析構函數Destroy(這有點像Java或.net的垃圾回收機制)。因此,咱們使用智能指針,能夠放心的建立,放心的引用,而不用去管何時該銷燬,徹底由後臺幫咱們實現。
下面把測試程序改一下,讓智能指針與集合結合測試:
測試結果:
一、智能指針與堆對象之間的循環引用
假如咱們把TTestClass類進行以下修改,讓堆對象擁有指向它智能指針的引用:
同時,把測試方法進行以下修改:
此時,咱們獲得了很是不靠譜的結果:
智能指針居然沒有自動釋放!
從上面的分析和前面的代碼咱們能夠看到,接口的引用計數爲0的時候,接口會自動釋放,咱們要保證接口可以被順利的釋放,必須保證接口的引用計數爲0。
從第 5* 點代碼咱們能夠看到,tt.Ap := ap,使得智能指針與堆對象之間進行了循環引用,致使接口ap的引用計數+1爲2。最後在方法退出的時候,雖然ap佔用的引用已經被釋放了,引用-1,可是因爲堆對象tt不會本身釋放,因此堆對象tt.Ap所佔用的引用沒有釋放,方法在退出時,接口的引用數爲1,接口沒有自動釋放。
二、什麼使用時候使用Release方法
首先咱們爲測試單元加入use:Generics.Collections,再將TTestClass類修改以下:
此時,成員變量再也不是一個值類型,而是一個引用類型。
將從DoTestAutoPtr方法開始代碼修改以下:
此時,咱們在DoTestAutoPtr方法內部建立了智能指針,並將智能指針所指向的堆對象傳給全局變量,而後在DoTestAutoPtr方法執行結束後調用全局變量的DoPrintInt方法。運行結果:
運行失敗了,緣由是在DoTestAutoPtr方法退出了之後,TAutoPtr<TTestClass>.New(TTestClass.Create(10))語句所建立的接口引用計數爲0,此時它會調用TTestClass的Destroy方法將fList銷燬。此時,咱們調用DoPrintInt方法,想獲得fList第一個元素,可是fList自己已經被銷燬了,因此致使錯誤的發生。
咱們將第6*行改成:
gTt := TAutoPtr<TTestClass>.New(TTestClass.Create(10)).Release;
運行結果:
此時不會出現錯誤,由於Release方法已經通知智能指針堆對象已經不受智能指針管理,因此在TAutoPtr<TTestClass>銷燬的時候不會調用 TTestClass的析構函數,fList得以保留下來。
在此處咱們能夠看到,因爲堆對象再也不受到智能指針的管理,因此咱們必須手動的將其釋放FreeAndNil(gTt),不然就會產生上圖所發生的結果:內存泄露。
剛開始實現棧對象我考慮過使用record,Delphi的record很是相似於類,保存在棧中,支持方法、屬性和帶參數的構造函數,可是不支持析構函數,因此沒有辦法實現咱們的智能指針。Delphi版的智能指針很早就在cnPack討論區中有前輩提出來過了(http://bbs.cnpack.org/viewthread.php?tid=1399),可是使用起來不方便致使這種寫法不怎麼流行。自從D2009支持泛型之後,之前不少實現起來很麻煩的功能如今都能很簡單的實現,如智能指針與泛型集合的結合。可是,在Delphi中使用智能指針是稍微有一些性能損失的,在目前電腦速度愈來愈快的今天,這點損失已經顯得微不足道了。
本隨筆全部源代碼打包下載:http://files.cnblogs.com/felixYeou/auto_ptr_code.rar