CLR 參考類別物件的宣告
從 Managed Extensions for C++ 升級為 Visual C++ 2010 之後,宣告和產生參考類別型別之物件的語法已變更。
在 Managed Extensions 中,會使用 ISO-C++ 指標語法宣告參考類別型別物件,而且可以選擇將 __gc 關鍵字放在星號 (*) 左邊。 例如,下列是在 Managed Extensions 語法中的各種參考類別型別物件宣告:
public __gc class Form1 : public System::Windows::Forms::Form {
private:
System::ComponentModel::Container __gc *components;
Button __gc *button1;
DataGrid __gc *myDataGrid;
DataSet __gc *myDataSet;
void PrintValues( Array* myArr ) {
System::Collections::IEnumerator* myEnumerator =
myArr->GetEnumerator();
Array *localArray;
myArr->Copy(myArr, localArray, myArr->Length);
}
};
在新語法中,您會使用新的宣告式語彙基元 (Token) (^) 宣告參考類別型別物件。這個語彙基元的正式名稱是「追蹤控制代碼」(Tracking Handle),但是通常會非正式地叫做「帽型符號」(Hat) (追蹤這個形容詞表示參考型別位於 CLR 堆積中,因此在記憶體回收堆積壓縮期間可以無障礙地移動位置)。 追蹤控制代碼在執行階段會無障礙地進行更新。 在實值型別語意中會討論下列兩個相似的概念:「追蹤參考」(%) 和「內部指標」(interior_ptr<>)。
要從重複使用 ISO-C++ 指標語法中移出宣告式語法,主要的原因如下:
使用指標語法不允許將多載運算子直接套用到參考物件。 相反地,您必須使用運算子的內部名稱來呼叫該運算子,例如使用 rV1->op_Addition(rV2),而不是比較容易了解的 rV1+rV2。
記憶體回收堆積上儲存的物件不允許進行一些指標作業,例如轉型和指標算術。 追蹤控制代碼的概念比較能夠表達 CLR 參考型別的本質。
不需要而且不支援追蹤控制代碼上的 __gc 修飾詞 (Modifier)。 物件本身的用法不變,一樣是透過指標成員選取運算子 (->) 存取成員。 例如,下列是將之前的 Managed Extensions 程式碼範例轉譯成新語法:
public ref class Form1: public System::Windows::Forms::Form {
private:
System::ComponentModel::Container^ components;
Button^ button1;
DataGrid^ myDataGrid;
DataSet^ myDataSet;
void PrintValues( Array^ myArr ) {
System::Collections::IEnumerator^ myEnumerator =
myArr->GetEnumerator();
Array ^localArray;
myArr->Copy(myArr, localArray, myArr->Length); }
};
CLR 堆積上的物件動態配置
在 Managed Extensions 中,是否存在兩個 new 運算式以便在原生和 Managed 堆積之間配置,在過去多數是一目了然的。 在幾乎任何情況下,編譯器都能夠利用內容來判斷要從原生或 Managed 堆積配置記憶體。 例如:
Button *button1 = new Button; // OK: managed heap
int *pi1 = new int; // OK: native heap
Int32 *pi2 = new Int32; // OK: managed heap
當您不想要內容堆積配置時,您可以用 __gc 或 __nogc 關鍵字指示編譯器。 在新語法中,會藉由引入 gcnew 關鍵字使兩個新運算式的個別本質變成明確。 例如,之前的三種宣告在新語法中看起來如下所示:
Button^ button1 = gcnew Button; // OK: managed heap
int * pi1 = new int; // OK: native heap
Int32^ pi2 = gcnew Int32; // OK: managed heap
下列是在上面區段中宣告的 Form1 成員的 Managed Extensions 初始化:
void InitializeComponent() {
components = new System::ComponentModel::Container();
button1 = new System::Windows::Forms::Button();
myDataGrid = new DataGrid();
button1->Click +=
new System::EventHandler(this, &Form1::button1_Click);
}
下列是重新轉型至新語法的相同初始化。 請注意,如果參考型別是 gcnew 運算式的目標,則不需要帽型符號。
void InitializeComponent() {
components = gcnew System::ComponentModel::Container;
button1 = gcnew System::Windows::Forms::Button;
myDataGrid = gcnew DataGrid;
button1->Click +=
gcnew System::EventHandler( this, &Form1::button1_Click );
}
沒有物件的追蹤參考
在新語法中,0 不再代表 Null 位址,而被視為整數,與 1、10 或 100 一樣。 新的特殊語彙基元 (Token) 代表追蹤參考的 Null 值。 例如,在 Managed Extensions 中,會初始化參考型別而不定址任何物件,如下所示:
// OK: we set obj to refer to no object
Object * obj = 0;
// Error: no implicit boxing
Object * obj2 = 1;
在新語法中,實值型別的任何初始化或指派至 Object 都會產生該實值型別的隱含 Boxing。 在新語法中,obj 和 obj2 都會初始化,以分別指向值為 0 和 1 的 Boxed Int32 物件。 例如:
// causes the implicit boxing of both 0 and 1
Object ^ obj = 0;
Object ^ obj2 = 1;
為了執行明確的初始化追蹤控制代碼、指派追蹤控制代碼,以及比較追蹤控制代碼與 Null,因此引入新關鍵字 nullptr。 原本範例的正確修訂如下所示:
// OK: we set obj to refer to no object
Object ^ obj = nullptr;
// OK: we initialize obj2 to a Int32^
Object ^ obj2 = 1;
這樣多少都使得將現有程式碼移植至新語法更為複雜。 例如,請參考下列實值類別宣告:
__value struct Holder {
Holder( Continuation* c, Sexpr* v ) {
cont = c;
value = v;
args = 0;
env = 0;
}
private:
Continuation* cont;
Sexpr * value;
Environment* env;
Sexpr * args __gc [];
};
此處,args 和 env 都是 CLR 參考型別。 在建構函式 (Constructor) 中將這兩個成員初始化為 0,這樣的做法在轉換至新語法時會不太一樣。 這兩個成員必須變更為 nullptr:
value struct Holder {
Holder( Continuation^ c, Sexpr^ v )
{
cont = c;
value = v;
args = nullptr;
env = nullptr;
}
private:
Continuation^ cont;
Sexpr^ value;
Environment^ env;
array<Sexpr^>^ args;
};
同樣地,將那些成員和 0 相較以進行測試的做法,也必須變成和 nullptr 相較。 下列是 Managed Extensions 語法:
Sexpr * Loop (Sexpr* input) {
value = 0;
Holder holder = Interpret(this, input, env);
while (holder.cont != 0) {
if (holder.env != 0) {
holder=Interpret(holder.cont,holder.value,holder.env);
}
else if (holder.args != 0) {
holder =
holder.value->closure()->
apply(holder.cont,holder.args);
}
}
return value;
}
以下是修訂,其中將出現的每個 0 都取代成 nullptr。 轉譯工具在這項轉換中助益良多,就算不是針對每次出現但也已經自動化大部分的項目,包括使用 NULL 巨集。
Sexpr ^ Loop (Sexpr^ input) {
value = nullptr;
Holder holder = Interpret(this, input, env);
while ( holder.cont != nullptr ) {
if ( holder.env != nullptr ) {
holder=Interpret(holder.cont,holder.value,holder.env);
}
else if (holder.args != nullptr ) {
holder =
holder.value->closure()->
apply(holder.cont,holder.args);
}
}
return value;
}
nullptr 會轉換為任何指標或追蹤控制代碼型別,但不會提升至整數型別。 例如,在下列初始化設定中,nullptr 只能當做前兩個項目的初始值。
// OK: we set obj and pstr to refer to no object
Object^ obj = nullptr;
char* pstr = nullptr; // 0 would also work here
// Error: no conversion of nullptr to 0 …
int ival = nullptr;
同樣地,指定方法的多載集合,如下所示:
void f( Object^ ); // (1)
void f( char* ); // (2)
void f( int ); // (3)
使用 nullptr 常值 (Literal) 的引動過程,如下所示,
// Error: ambiguous: matches (1) and (2)
f( nullptr );
為模稜兩可,因為 nullptr 同時符合追蹤控制代碼和指標,而且兩種型別的偏好程度相同 (這種情況會需要明確轉型以解除模稜兩可的情況)。
使用 0 的引動過程完全符合執行個體 (3):
// OK: matches (3)
f( 0 );
因為 0 屬於整數型別。 如果沒有 f(int),呼叫會透過標準轉換明確地符合 f(char*)。 比對規則認定完全相符的優先順序高於標準轉換。 如果缺少完全相符,則標準轉換優於實值型別的隱含 Boxing。 這就是不會模稜兩可的原因。
請參閱
參考
^ (Handle to Object on Managed Heap)