2.4.4聲明一個引用對象
修訂版語言支持在本地棧上聲明引用類的對象,或者聲明為類的成員,就像它可以直接被訪問一樣(注意這在 Microsoft Visual Studio 2005 的Beta1 發布版中不可用)。析構函數和在 2.4.3 節中描述的 Dispose()方法結合時,結果就是引用類型的終止語義的自動調用。使 CLI 社區苦惱的非確定性終止這條暴龍終于被馴服了,至少對于 C++/CLI的用戶來說是這樣。讓我們看一下這到底意味著什么。
首先,我們這樣定義一個引用類,使得對象創建函數在類構造函數中獲取一個 資源。其次,在類的析構函數中,釋放對象創建時獲得的資源。
public ref class R { public: R() { /* 獲得外部資源 */ } ~R(){ /* 釋放外部資源 */ } // ... 雜七雜八 ... };
對象聲明為局部的,使用沒有附加"帽子"的類型名。所有對對象的使用(如調用成員函數)是通過成員選擇點 (.) 而不是箭頭 (->) 完成的。在塊的末尾,轉換成 Dispose()的相關的析構函數被自動調用。
void f() { R r; r.methodCall(); // ... // r被自動析構 - // 也就是說, r.Dispose() 被調用... }
相對于 C#中的 using語句來說,這只是語法上的點綴而已,而不是對基本 CLI約定(所有引用類型必須在 CLI堆上分配)的違背?;A語法仍未變化。用戶可能已經編寫了下面同樣功能的語句(這很像編譯器執行的內部轉換):
// 等價的實現... // 除了它應該位于一個 try/finally 語句中之外 void f() { R^ r = gcnew R; r->methodCall(); // ... delete r; }
事實上,在修訂版語言設計中,析構函數再次與構造函數配對成為和一個局部對象生命周期關聯的自動獲得/釋放資源的機制。這個顯著的成就非常令人震驚,并且語言設計者應該因此被大力贊揚。
2.4.5聲明一個顯式的Finalize()-(!R)
在修訂版語言設計中,如我們所見,構造函數被合成為 Dispose()方法。這意味著在析構函數沒有被顯式調用的情況下,垃圾回收器在終止過程中,不會像以前那樣為對象查找相關的 Finalize()方法。為了同時支持析構函數和終止,修訂版語言引入了一個特殊的語法來提供一個終止器。舉例來說:
public ref class R { public: !R() { Console::WriteLine( "I am the R::finalizer()!" ); } };
!前綴表示引入類析構函數的類似符號 (~),也就是說,兩種后生命周期的方法名都是在類名前加一個符號前綴。如果派生類中有一個合成的 Finalize()方法,那么在其末尾會插入一個基類的 Finalize()方法的調用。如果析構函數被顯式地調用,那么終止器會被抑制。這個轉換如下所示:
// V2 中的內部轉換 public ref class R { public: void Finalize() { Console::WriteLine( "I am the R::finalizer()!" ); } };
2.4.6這在V1到V2的轉換中意味著什么
這意味著,只要一個引用類包含一個特別的析構函數,一個 V1程序在 V2 編譯器下的運行時行為被靜默地修改了。需要的轉換算法如下所示:
• |
如果析構函數存在,重寫它為類終止器方法。 |
• |
如果 Dispose()方法存在,重寫到類析構函數中。 |
• |
如果析構函數存在,但是 Dispose()方法不存在,保留析構函數并且執行第 (1) 項。 |
在將代碼從 V1移植到 V2的過程中,可能漏掉執行這個轉換。如果應用程序某種程度上依賴于相關終止方法的執行,那么應用程序的行為將被靜默地修改。
屬性和操作符的聲明在修訂版語言設計中已經被大范圍重寫了,隱藏了原版設計中暴露的底層實現細節。另外,事件聲明也被修改了。
在 V1中不受支持的一項更改是,靜態構造函數現在可以在類外部定義了(在 V1中它們必須被定義為內聯的),并且引入了委托構造函數的概念。
在原版語言設計中,每一個 set或者 get屬性存取方法都被規定為一個獨立的成員函數。每個方法的聲明都由 __property關鍵字作為前綴。方法名以 set_或者 get_開頭,后面接屬性的實際名稱(如用戶所見)。這樣,一個獲得向量的 x坐標的屬性存取方法將命名為 get_x,用戶將以名稱 x來調用它。這個名稱約定和單獨的方法規定實際上反映了屬性的基本運行時實現。例如,以下是我們的向量,有一些坐標屬性:
public __gc __sealed class Vector { public: // ... __property double get_x(){ return _x; } __property double get_y(){ return _y; } __property double get_z(){ return _z; } __property void set_x( double newx ){ _x = newx; } __property void set_y( double newy ){ _y = newy; } __property void set_z( double newz ){ _z = newz; } };
這使人感到迷惑,因為屬性相關的函數被展開了,并且需要用戶從語法上統一相關的 set和 get。而且它在語法上過于冗長,并且感覺上不甚優雅。在修訂版語言設計中,這個聲明更類似于 C# — property 關鍵字后接屬性的類型以及屬性的原名。set存取和get存取方法放在屬性名之后的一段中。注意,與 C# 不同,存取方法的符號被指出。例如,以下是上面的代碼轉換為新語言設計后的結果:
public ref class Vector sealed { public: property double x { double get() { return _x; } void set( double newx ) { _x = newx; } } // Note: no semi-colon ... };
如果屬性的存取方法表現為不同的訪問級別 — 例如一個公有的 get和一個私有的或者保護的 set,那么可以指定一個顯式的訪問標志。默認情況下,屬性的訪問級別反映了它的封閉訪問級別。例如,在上面的 Vector定義中,get和 set方法都是公有的。為了讓 set方法成為保護或者私有的,必須如下修改定義:
public ref class Vector sealed { public: property double x { double get() { return _x; } private: void set( double newx ) { _x = newx; } } // 注意:private 的作用域到此結束 ... //注意:dot 是一個 Vector 的公有方法... double dot( const Vector^ wv ); // etc. };
屬性中訪問關鍵字的作用域延伸到屬性的結束括號或者另一個訪問關鍵字的說明。它不會延伸到屬性的定義之外,直到進行屬性定義的封閉訪問級別。例如,在上面的聲明中,Vector::dot()是一個公有成員函數。
為三個 Vector坐標編寫 set/get屬性有點乏味,因為實現的本質是定死的:(a) 用適當類型聲明一個私有狀態成員,(b) 在用戶希望取得其值的時候返回,以及 (c) 將其設置為用戶希望賦予的任何新值。在修訂版語言設計中,一個簡潔的屬性語法可以用于自動化這個使用方式:
public ref class Vector sealed { public: //等價的簡潔屬性語法 property double x; property double y; property double z; };
簡潔屬性語法所產生的一個有趣的現象是,在編譯器自動生成后臺狀態成員時,除非通過 set/get訪問函數,否則這個成員在類的內部不可訪問。這就是所謂的嚴格限制的數據隱藏!
原版語言對索引屬性的支持的兩大缺點是不能提供類級別的下標,也就是說,所有索引屬性必須有一個名字,舉例來說,這樣就沒有辦法提供可以直接應用到一個 Vector或者Matrix類對象的托管下標操作符。其次,一個次要的缺點是很難在視覺上區分屬性和索引屬性 — 參數的數目是唯一的判斷方法。最后,索引屬性具有與非索引屬性同樣的問題 — 存取函數沒有作為一個基本單位,而是分為單獨的方法。舉例來說:
public __gc class Vector; public __gc class Matrix { float mat[,]; public: __property void set_Item( int r, int c, float value); __property int get_Item( int r, int c ); __property void set_Row( int r, Vector* value ); __property int get_Row( int r ); };
如您所見,只能用額外的參數來指定一個二維或者一維的索引,從而區分索引器。在修訂版語法中,索引器由名字后面的方括號 ([,]) 區分,并且表示每個索引的數目和類型:
public ref class Vector; public ref class Matrix { private: array<float, 2>^ mat; public: property int Item [int,int] { int get( int r, int c ); void set( int r, int c, float value ); } property int Row [int] { int get( int r ); void set( int r, Vector^ value ); } };
在修訂版語法中,為了指定一個可以直接應用于類對象的類級別索引器,重用 default關鍵字以替換一個顯式的名稱。例如:
public ref class Matrix { private: array<float, 2>^ mat; public: //OK,現在有類級別的索引器了 // // Matrix mat ... // mat[ 0, 0 ] = 1; // // 調用默認索引器的 set 存取函數... property int default [int,int] { int get( int r, int c ); void set( int r, int c, float value ); } property int Row [int] { int get( int r ); void set( int r, Vector^ value ); } };
在修訂版語法中,當指定了 default索引屬性時,下面兩個名字被保留:get_Item和set_Item。這是因為它們是 default索引屬性產生的底層名稱。
注意,簡單索引語法與簡單屬性語法截然不同。
聲明一個委托和普通事件僅有的變化是移除了雙下劃線,如下面的示例所述。在去掉了之后,這個更改被認為是完全沒有爭議的。換句話說,沒有人支持保持雙下劃線,所有人現在看來都同意雙下劃線使得原版語言感覺很難看。
// 原版語言 (V1) __delegate void ClickEventHandler(int, double); __delegate void DblClickEventHandler(String*); __gc class EventSource { __event ClickEventHandler* OnClick; __event DblClickEventHandler* OnDblClick; // ... }; // 修訂版語言 (V2) delegate void ClickEventHandler( int, double ); delegate void DblClickEventHandler( String^ ); ref class EventSource { event ClickEventHandler^ OnClick; event DblClickEventHandler^ OnDblClick; // ... };
事件(以及委托)是引用類型,這在 V2中更為明顯,因為有帽子 (^) 的存在。除了普通形式之外,事件支持一個顯式的聲明語法,用戶顯式指定事件關聯的 add()、raise()、和 remove()方法。(只有 add()和 remove()方法是必須的;raise()方法是可選的)。
在 V1設計中,如果用戶選擇提供這些方法,盡管她必須決定尚未存在的事件的名稱,她也不必提供一個顯式的事件聲明。每個單獨的方法以 add_EventName、raise_EventName、和 remove_EventName的格式指定,如以下引用自 V1語言規范的示例所述:
// 原版 V1 語言下 // 顯式地實現 add、remove 和 raise ... public __delegate void f(int); public __gc struct E { f* _E; public: E() { _E = 0; } __event void add_E1(f* d) { _E += d; } static void Go() { E* pE = new E; pE->E1 += new f(pE, &E::handler); pE->E1(17); pE->E1 -= new f(pE, &E::handler); pE->E1(17); } private: __event void raise_E1(int i) { if (_E) _E(i); } protected: __event void remove_E1(f* d) { _E -= d; } };
該設計的問題主要是感官上的,而不是功能上的。雖然設計支持添加這些方法,但是上面的示例看起來并不是一目了然。因為 V1屬性和索引屬性的存在,類聲明中的方法看起來千瘡百孔。更令人沮喪的是缺少一個實際的 E1事件聲明。(再強調一遍,底層實現細節暴露了功能的用戶級別語法,這顯然增加了語法的復雜性。)這只是勞而無功。V2設計大大簡化了這個聲明,如下面的轉換所示。事件在事件聲明及其相關委托類型之后的一對花括號中指定兩個或者三個方法如下所示:
// 修訂版 V2 語言設計 delegate void f( int ); public ref struct E { private: f^ _E; //是的,委托也是引用類型 public: E() { // 注意 0 換成了 nullptr! _E = nullptr; } // V2 中顯式事件聲明的語法聚合 event f^ E1 { public: void add( f^ d ) { _E += d; } protected: void remove( f^ d ) { _E -= d; } private: void raise( int i ) { if ( _E ) _E( i ); } } static void Go() { E^ pE = gcnew E; pE->E1 += gcnew f( pE, &E::handler ); pE->E1( 17 ); pE->E1 -= gcnew f( pE, &E::handler ); pE->E1( 17 ); } };
雖然在語言設計方面,人們因為語法的簡單枯燥而傾向于忽視它,但是如果對語言的用戶體驗有很大的潛移默化的影響,那么它實際上很有意義。一個令人迷惑的、不優雅的語法可能增加開發過程的 風險,很大程度上就像一個臟的或者不清晰的擋風玻璃增加開車的風險一樣。在修訂版語言設計中,我們努力使語法像一塊高度磨光的新安裝的擋風玻璃一樣透明。
原文轉自:http://www.anti-gravitydesign.com