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