Viusal Studio2005極大地豐富了它的庫,可以說是科研背後的清障機和加速器,對於這一點,我想大部分人都是這麼認為的。它帶來的大量工具及新增的功能性函數使開發人員的生活越來越快樂、簡單。但對於我來說,所有這些與Visual Studio2005在C++上做的改變相比卻都顯得是那麼蒼白無力。這篇文章中,我著重敘述即將向使用者發行的Viusal Studio2005版本給C++帶來的變化。
一、對底線說再見
Visual Studio.NET 2002在C++中引入了可擴充的託管,這種擴充帶來的關鍵詞以雙底線開始,例如__gc 和 __property。這個版本發行後的這些年來,我寫了大量帶有雙底線的代碼,我不得不承認我從來都不喜歡這一點,我完全明白真正的原因是什麼:雙底線將關鍵詞標誌為特殊地擴充,以區分編譯器的標準編譯規則,在理論上,可以充分使用可擴充的託管,使用其他的編譯器編譯它,這將忽略所有帶雙底線關鍵詞。
解決方案:微軟發現了一個解決方案來改變這種語言而不是替換這種語言。但是這種妥協帶來了以下結果:
1、開發人員發現這種文法不自然而且看上去也很不舒服。
2、不能盡其所能。
例如,下面是託管C++聲明屬性的例子:
public __gc class Foo { // hundreds of lines of code __property String* get_Text(); // hundreds of lines of code __property void set_Text(String*); // hundreds of lines of code }; |
我相信,有良好編程習慣的程式員會將get與set緊挨者使用,並且會緊接著又聲明所有下面需要使用的變數。但是語言並不管這些,它不能提供封閉的括弧來界定結構,來讓你聲明"這是一個作為單元的屬性"。所以當它運行時顯得不自然並且與其他.NET語言也格格不入。
面對這些你能做什嗎?唯一的方法是將C++與CLI自然地結合起來,反之也就是真正改變C++。如果你將這麼作,一種自然完美的語言將給你帶來巨大的自由,當你編程時就再也不會需要雙底線了。
二、生命期與範圍
我非常喜歡明確地銷毀對象。實際上,我也很喜歡垃圾收集器。可能我要說的更多,事實上,雖然它們有著各地的位置,而且對於我來說都需要,但如果我正在建立的對象僅僅操作記憶體,如果使用後不需要我釋放內容我將會非常高興。但記憶體管理是如此的虛弱無力,當我的對象佔用非託管的資源時,例如一個資料庫連接,一個檔案對象或類似的對象,我需要自己控制。我需要確認一旦不需要的時候它就消亡。Dispose模式試圖處理這些情況,但它並不是自覺的行為。封閉的括弧也許是一種很好地解決途徑。
在普通的非託管C++中,以下的代碼說明了你不得不做的工作:
//this is a code fragment { try { Foo* f=new Foo(/* params */); //all kinds of code, some of which might throw exceptions delete f; } catch (/* something */) { delete f; //whatever else, or rethrow; } } |
如果在堆上建立對象顯的是那麼的容易:
//this is a code fragment { Foo f; //all kinds of code, some of which might throw exceptions } |
當變數f超出範圍,無論是否是因為異常,它都自動消亡,這非常自然而且令人高興、滿意。
當這個對象在託管堆上,你不需要刪除它,它將被垃圾搜集器清除。但是,如果它佔用了一個託管資源,你可能想通過Dispose()方法來清除它,C#為這麼做提供了using構造,但是它仍然不象我們的堆例子那樣簡單。
在新版本的語言中(以前叫C++/CLI),你可以不依賴於對象的種類來建立它,你可以在堆上建立一個託管對象,並且它可以在超出範圍後明確地被銷毀。如果願意的話,你還可以在託管堆上建立,這完全根據你的選擇而定。
這種變化帶來了其他的後果。最具深遠意義的結果是你可以輕鬆地將任意對象放入樣板集或作為另外一個類的成員變數。你可以充分發揮C++的力量來管理對象的生命期,而不是僅僅在堆上分配它並等待垃圾處理器來處理。
三、析構和終結
當你書寫一個可以被其他語言使用的垃圾收集對象時,將發生什嗎?你針對這個對象是否已經寫了一個解構函式?當你正在使用C++,你可以在堆上建立對象,當超出範圍範圍後,對象的解構函式將自動運行。當C#或VB應用(不能在堆上建立垃圾收集對象)使用這個對象時將發生什嗎?這種情況被以一種精巧的方法即時處理。它將對象的解構函式轉換為Dispose()方法來使用,所以,擁有解構函式的C++/CLI對象都可以任意使用。
如果你用C#或VB寫了一個帶有Dispose()方法的類,你可能已經寫了一個終結函數,對於終結函數C++/CLI也有一種非常簡單的文法。就象foo對象的解構函式叫做~Foo()一樣,foo對象的終結函數叫做!Foo(),這兩種方法都提醒你他們與建構函式相反。
當一個對象在託管堆上建立後,終結函數開始運行但從不被處理(因為執行的Dispose的層級要高於終結函數)。從某種意義上說,它是一個防護網,使你確信對象能釋放其佔用的非託管資源。即使使用對象的開發人員忘記了處理它。
四、指標與控制代碼
對於擴充的託管C++來說,一個主要的限制是C++語言沒有變化,兩種不同的事情使用相同的符號。"*"的意思要根據代碼的上下文而定,試著看看下行代碼:
Foo對象在那裡建立?記憶體是否會被自動清除?是否可以象下面代碼那樣對於指標使用如下演算法:
答案依賴於foo是否使用__gc關鍵詞聲明,如果它是一個垃圾收集對象,它只能在託管堆上建立,而不能在本地堆、不能在棧上建立。另一方面,如果沒有使用__gc關鍵詞聲明,這行代碼將在本地堆上給對象分配記憶體,這時候你必須記住使用"delete"來釋放它。
一旦編譯器的作者擁有了修改語言的自由,正如C++/CLI已經發生的那樣,你大可不必擔心類對象來自那裡,它在哪裡存在。你完全可以通過不同的文法來告之對象它應該存在在哪裡。
這叫做一個控制代碼,起初絕大多數C++開發組將^這個符號稱為"脫字元號或帽子",現在都叫做帽子。就象指標那樣,你從處理*或->符號中解脫出來。這種變化帶來的衝擊絕大部分是在你的頭腦裡,你只要看執行個體的聲明就能判斷對象生命期的管理,而不用返回查看類的聲明。
說到類的聲明,__gc and __nogc已經不再存在,在它們的位置上是一些比較"Cool"的、帶有空格的關鍵詞,帶空格的關鍵詞雖然看上去是兩個詞,但實際上是含有空格的一個詞。
例如:
ref class R { private: int m_a; public: R(int a): m_a(a) {} }; |
你可能認為"ref"是C++/CLI中的一個新關鍵詞,但實際上它不是,"Ref class"是一個關鍵詞,其他的一些類似關鍵詞是:"value class",""interface class"和"enum class"。因為以前幾乎寫的每一個C++程式都含有一個叫"Value"的變數,我非常高興它沒有變成一個關鍵詞。
一個ref class 是一個託管類,一個生存在託管堆上並由垃圾收集器管理的類。正如我先前說的,你可以在棧上建立執行個體,編譯器將通過一個隱藏的靈巧的指標來管理該對象。
五、屬性
C++的屬性變化非常大,因為我在文章的開篇講述了託管C++中屬性的尷尬,現在就讓我們以簡潔的C++/CLI版本來結束吧。
ref class R { private: int m_Size; public: property int Size { int get() { return m_Size; } void set(int val){m_Size = val;} } }; R r; r.Size = 42; |
屬性是否是關鍵詞,從某種意義上可以這麼說,它是一個定位型的關鍵詞,所以你可以毫無衝突地將一個變數或函數稱為屬性,就象上面的代碼那樣,它只是在類的定義中有特殊的含義。現在的C++/CLI語言支援將屬性定義為一個單獨的單元,相對與以往的方式,我更喜歡新的方式,相信你也是這樣。