C++/CLI中的资源清理(Destructor,Finalizer)
本文將分成三部分,他們分別是引言、Destructor,Finalizer的語法表示、如何保證Destructor,Finalizer與其他語言兼容。 
 ? 一、 引言 
 ?? 資源是一個很大的范疇,先讓我確定一下我們這里談?wù)摰馁Y源包括哪些內(nèi)容。這里專指在面向?qū)ο缶幊讨幸粋€對象實例所使用的資源,他包括對象本身所占有的內(nèi)存(對象占有內(nèi)存的大小由對象字段成員來決定,字段成員越多占有的內(nèi)存就越大)以及其字段成員(Field member)所使用的資源,如文件句柄,數(shù)據(jù)庫鏈接等等。相信大家比我到清楚在一個對象不再被使用時應(yīng)該釋放其占有的資源,在清理對象所占有的內(nèi)存之前,執(zhí)行一個特定的函數(shù),釋放字段成員所使用的資源。比如一個文件對象,我們在delete之前得調(diào)用Close函數(shù)。C++/CLI中的析構(gòu)器(Destructor)、終結(jié)器(Finanlizer)便扮演這個特定函數(shù)的角色。在探討這兩個函數(shù)之前我們先回憶一下與C++/CLI有著一定關(guān)系的ISO C++與.NET平臺(ISO C++是他的前輩,并且C++/CLI對ISO C++是兼容的,他們的兼容性已超出本文范圍,我們可以在往后再一起討論;.NET平臺是C++/CLI的運行平臺),看看他們倆是如何完成資源清理的,這樣能夠幫助我們更好地理解C++/CLI中的資源清理。 
 ?? 
 ?? ISO C++面對的是無虛擬機環(huán)境,直接根操作系統(tǒng)或是硬件打交道,資源的回收必須由程序員完成,即在某個對象不再使用時得手動地進行資源釋放。如果是棧對象則在超出作用域時會自動調(diào)用析構(gòu)器,同時釋放對象自己所占有的內(nèi)存;若是堆對象,只有程序員使用delete pointer 時才會調(diào)用pointer所指向?qū)ο蟮奈鰳?gòu)器,接著釋放pointer所指向?qū)ο蟮膬?nèi)存。 
 ?? 
 ?? .NET平臺的一個主要特點是,托管內(nèi)存,內(nèi)存的回收交給垃圾回收器(GC)來管理。它會檢測到哪些對象不再被使用,便回收其所占用的內(nèi)存。如果該對象所屬的類型實現(xiàn)了Finalize()函數(shù),則會在回收內(nèi)存之前調(diào)用該函數(shù)。Finalize函數(shù)的作用與ISO C++中的析構(gòu)器作用類似,在對象被銷毀之前釋放其字段成員使用的其他資源。 
 ?? 
 ?? 比較一下ISO C++與.NET平臺的資源清理,我們不難發(fā)現(xiàn),ISO C++的資源清理是手動的、確定的(調(diào)用時機我們可以控制);.NET平臺下的資源釋放是自動的、不確定的(調(diào)用時機我們不能控制)。他們的各自缺點是, ISO C++程序員關(guān)注哪些對象不再被使用,調(diào)用delete pointer,否則會造成資源泄漏;.NET平臺下的一些稀缺資源不能得及時的釋放。 
 ?? 
 ?? 在C++/CLI中,析構(gòu)器與終結(jié)器同時出現(xiàn),解決了了ISO C++與.NET平臺的不足,當然同時也沒有丟失他們的優(yōu)點。如果對象所使用的資源是稀缺的,必須確定性的釋放,我們便可調(diào)用析構(gòu)器,要是萬一我們遺忘了調(diào)用析構(gòu)器,最后垃圾回收器(GC)會調(diào)用幫我們調(diào)用終結(jié)器。到此我們現(xiàn)在可以斷言,析構(gòu)器與終結(jié)器所做的事情是一樣的,釋放其字段成員所使用的資源,只是調(diào)用者不一樣,一個是程序員一個是垃圾回收器(GC)。最后再補充一下,這里所說的釋放其字段成員所使用的資源,并不包括字段本身使用的內(nèi)存。比如說一個對象中包含有一個文件句柄字段,他會占用4 個Byte的內(nèi)存,這塊內(nèi)存資源在析構(gòu)器或終結(jié)器中都不會被回收,他的回收將發(fā)生在最后,垃圾回收器回收托管內(nèi)存時。 
 ?? 
 ?? 我們了解了析構(gòu)器與終結(jié)器存在的意義自后,接下來我了解一下他們的語法表示。 
 ?? 
 ?? 
 ? 二、 Destructor,Finalizer的語法表示 
 ? ~ClassName(){…..} //析構(gòu)器,”~”加類名稱 
 ?? 
 ? !ClassName(){…...} //終結(jié)器,”!”加類名稱 
 ?? 
 ? 假如我們定義一個MyClass類型,他們語法表示如下: 
 ?? 
 ?? 
 ?? 
 ? public ref class MyClass 
 ?? 
 ? { 
 ?? public: 
 ?? ~MyClass() //析構(gòu)器 
 ?? {…} 
 ?? !MyClass() //終結(jié)器 
 ?? {…} 
 ? }; 
 ?? 
 ?? 
 ?? 
 ?? 從第一部分的內(nèi)容我們知道析構(gòu)器與終結(jié)器是一樣的,為了避免代碼的重復(fù)我們可以在析構(gòu)器中調(diào)用終結(jié)器(MSDN推薦這樣調(diào)用,見Destructors and Finalizers in Visual C++,目前還沒弄明白為什么是析構(gòu)器調(diào)用終結(jié)器,從函數(shù)調(diào)用上來看他們兩誰調(diào)用誰都是一樣的),于是我們的代碼可以表現(xiàn)如下。 
 ?? 
 ? public ref class MyClass 
 ? { 
 ?? public: 
 ?? ~MyClass() //析構(gòu)器 
 ?? { 
 ?? this->!MyClass(); //在析構(gòu)器中調(diào)用析構(gòu)器 
 ?? } 
 ?? !MyClass() //終結(jié)器 
 ?? { 
 ?? //TODO,釋放字段成員所使用的資源 
 ?? } 
 ? }; 
 ?? 
 ?? 
 ?? 
 ?? 
 ?? 
 ?? 
 ? MyClass ^myClass = gcnew MyClass(); 
 ?? 
 ? delete myClass; //調(diào)用析構(gòu)器 
 ?? 
 ? myClass = nullptr; //讓 myClass 指向空對象, 這樣便于垃圾回收器回收myClass原先所向?qū)ο笏加械膬?nèi)存。在此再啰唆一下delete關(guān)鍵字,C++/CLI保留了ISO C++ delete關(guān)鍵字,但是他語意有了一些變化。在ISO C++中,他會先調(diào)用指針所指向?qū)ο蟮奈鰳?gòu)器,然后釋放對象所占用的內(nèi)存。在C++/CLI中只會調(diào)用析構(gòu)器,對象所占有的內(nèi)存不會馬上被回收,最終交給垃圾回收器來回收。這源于內(nèi)存模型的變化,C++/CLI中的引用類型只能分配在托管內(nèi)存中,托管內(nèi)存是不能由程序員來顯示回收的(當然GC.Collection()函數(shù)可以顯示讓垃圾回收器進行內(nèi)存回收,當并不建議這樣做,除非一些特殊情況)。 
 ?? 
 ?? 大家都知道.NET平臺支持多種語言,為了保證不同語言編寫的的類型之間能夠很好地相互訪問,微軟定義了一個通用語言規(guī)范(Common Language Specification,簡稱CLS),滿足CLS的類型及類型成員便可在不同語言間無縫交互。那么接下來就讓我們探討一下Destructor,Finalizer是如何做到與其他語言兼容的。 
 ?? 
 ?? 
 ? 三、如何保證Destructor,Finalizer與其他語言兼容 
 ? 這里我們就從C#的角度出發(fā),分析其是如何實現(xiàn)Destructor,Finalizer與C#的兼容,即如何在C#中訪問Destructor,Finalizer并保持語意清晰,如果我們直接使用函數(shù)名來訪問(~ClassName(),!ClassName()),那看起來就不那么完美了。 
 ?? 
 ?? 查看C++/CLI Language Specification,得知C++/CLI不允許程序員顯示的實現(xiàn)(implement)IDisposable接口,當我們給一個類型寫析構(gòu)器時,編譯器會自動將讓類型實現(xiàn)IDisposable接口。析構(gòu)器的確定資源釋放與IDisposable接口的語意一樣(MSDN, IDisposable 定義一種釋放分配的非托管資源的方法),在C#中調(diào)用Dispose()函數(shù)便是我們實現(xiàn)的析構(gòu)器。終結(jié)器從名稱上我們很容易想到Finalize()函數(shù),只要編譯器給我們生成Finalize()函數(shù),并在該函數(shù)中調(diào)用!ClassName()函數(shù)即可。編譯器如何處理這一問題,請看以下的代碼與注釋。 
 ?? MyClass類型的C++/CLI代碼如下: 
 ?? 
 ?? 
 ?? 
 ? using namespace System; 
 ? using namespace System::IO; 
 ? public ref class MyClass 
 ? { 
 ? public: 
 ?? MyClass() //構(gòu)造函數(shù),初始化字段成員 
 ?? { 
 ?? m_stream = gcnew FileStream("C:\\test.txt",FileMode::Create,FileAccess::Write); 
 ?? m_writer = gcnew StreamWriter(m_stream); 
 ?? m_writer->Write("test"); 
 ?? } 
 ?? ~MyClass() //析構(gòu)器 
 ?? { 
 ?? this->!MyClass(); //調(diào)用終結(jié)器 
 ?? } 
 ?? !MyClass() //終結(jié)器 
 ?? { 
 ?? delete m_writer; //釋放字段成員所占有的資源 
 ?? delete m_stream; //釋放字段成員所占有的資源 
 ?? } 
 ? private: 
 ?? FileStream ^m_stream; //字段 
 ?? StreamWriter ^m_writer; //字段 
 ? } 
 ?? 
 ?? 以下在為通過Reflector查看的編譯結(jié)果: 
 ?? 
 ?? 
 ?? 
 ? public class MyClass : IDisposable /**//*C++/CLI源碼中并沒有顯示實現(xiàn)該接口,是編譯器給我們加上的*/ 
 ? { 
 ?? // Fields 
 ?? private FileStream m_stream; 
 ?? private StreamWriter m_writer; 
 ?? 
 ? // Methods 
 ? /**//*注意一下三個方法,從名稱上,我們不難發(fā)現(xiàn)他們就是我們在MyClass實現(xiàn)時所寫的三個方法。只有構(gòu)造保持沒變。析構(gòu)器與終結(jié)器前面都加上了private訪問修飾符,因此在類型之外是無法訪問到析構(gòu)器和終結(jié)器的。*/ 
 ?? private void !MyClass(); 
 ?? public MyClass(); 
 ?? private void ~MyClass(); 
 ?? 
 ? /**//*以下三個函數(shù)是編譯器給我們加上的,他們是實現(xiàn)與CLS兼容的關(guān)鍵,請注意他們的邏輯關(guān)系,邏輯關(guān)系也比較簡單,看一下代碼就清楚了。建議閱讀時我們不妨試著在腦海里執(zhí)行一下,Dispose(),Finalizer()方法。*/ 
 ?? 
 ? // Dispose()為IDisposable成員。 
 ? public sealed override void Dispose() 
 ? { 
 ?? this.Dispose(true); 
 ?? //傳true調(diào)用Dispose(bool) 函數(shù) 
 ?? GC.SuppressFinalize(this); 
 ?? /**//*如果用戶調(diào)用了Dispose(),通知垃圾回收器不再執(zhí)行Finalize函數(shù)*/ 
 ? } 
 ?? 
 ?? /**//*這就是我們先前說的,由垃圾回收器調(diào)用的函數(shù)*/ 
 ? protected override void Finalize() 
 ? { 
 ?? this.Dispose(false); 
 ?? //傳false調(diào)用Dispose(bool) 函數(shù) 
 ? } 
 ?? 
 ? /**//*關(guān)鍵是下面這個函數(shù)Dispose(bool),他確保了我們在別的語言或垃圾回收器可以調(diào)到正確的函數(shù),C++/CLI中析構(gòu)器,終結(jié)器*/ 
 ? protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool flag1) 
 ? { 
 ?? if (flag1) 
 ?? { 
 ?? this.~MyClass(); 
 ?? /**//*Dispose() 調(diào)用該函數(shù)傳入true,便執(zhí)行該語句塊,實現(xiàn)了C++/CLI與CLS兼容,調(diào)用Dispose(),便是C++/CLI中的析構(gòu)器,確定性資源釋放*/ 
 ?? } 
 ?? else 
 ?? { 
 ?? try 
 ?? { 
 ?? this.!MyClass(); 
 ?? /**//*Finalize() 調(diào)用該函數(shù)傳入false,便執(zhí)行該語句塊,實現(xiàn)了C++/CLI與CLS兼容,調(diào)用Finalize(),便是C++/CLI中的終結(jié)器,確保對象在被銷毀前釋放其占有的其他資源*/ 
 ?? } 
 ?? finally 
 ?? { 
 ?? base.Finalize(); 
 ?? } 
 ?? } 
 ? } 
 ? }?
 ?? 
 
?
總結(jié)
以上是生活随笔為你收集整理的C++/CLI中的资源清理(Destructor,Finalizer)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: HTML5 飞鸽传书web servic
 - 下一篇: 飞鸽传书 宣传单和电话说辞