在托管堆上分配對象實(shí)例,似乎是使用托管擴(kuò)展C++、C#、J#、VB.NET程序員的方法,而使用本地C++的程序員,不但可以在堆上分配內(nèi)存,甚至更慣于使用基于堆棧的對象實(shí)例。
現(xiàn)在回顧一下以前定義的Point引用類,再來看一下以下變量定義:
Point p1, p2(3,4);
從本地C++的角度來說,p1與p2應(yīng)為基于堆棧的引用類Point實(shí)例,哪怕是從一般性的角度來看,它們也是。P1由默認(rèn)的構(gòu)造函數(shù)初始化,而p2由接受x與y坐標(biāo)的構(gòu)造函數(shù)初始化。從實(shí)現(xiàn)上來看,Point是自包含類型的(也就是說,它不包含任何指針或句柄),然而,作為一個引用類的實(shí)例,它仍處于CLI運(yùn)行時的掌控之下,且在必要時,會被垃圾回收--正因?yàn)榇?,所以不能定義一個引用類的靜態(tài)或全局實(shí)例。
同時,也不能將sizeof應(yīng)用于指明是引用類實(shí)例的表達(dá)式,因?yàn)閟izeof是在編譯時進(jìn)行計算的,而Point對象的大小要直到運(yùn)行時才能確定;但是,可將sizeof應(yīng)用于句柄,因?yàn)樗拇笮≡诰幾g時就已經(jīng)確定了。
另外,還不能定義一個基于堆棧的CLI數(shù)組實(shí)例。
跟蹤引用
本地C++可通過&來定義一個對象的別名,例如,對任意本地類N,可編寫如下代碼:
N n1;
N& n2 = n1;
引用必須在定義時進(jìn)行初始化,且在整個生命期中,它們都鎖定于引用同一對象,也就是說,它的值不會改變。引用一個引用類的實(shí)例與引用一個本地類基本一致,只不過語法不同而已。
在程序執(zhí)行期間,引用類的實(shí)例會在內(nèi)存中"移動",所以,需要對它們進(jìn)行跟蹤,而本地指針與引用卻不能夠勝任這項(xiàng)工作(尤其指不能對一個引用類的實(shí)例使用取地址符&),因此,C++/CLI對應(yīng)地提供了句柄及用于跟蹤的引用--在此簡稱為跟蹤引用(Tracking References),例如,你可以定義一個跟蹤引用p3,以追蹤對象p2:
Point% p3 = p2;
跟蹤引用的內(nèi)存存儲方式必須為自動(atuomatic),另外,盡管本地對象不會在內(nèi)存中"移動",但在上面的n2中,不能使用%來代替&。在C++/CLI中,%之于^,就如同本地C++中的&之于*。
請看下列代碼:
Point^ hp = gcnew Point(2,5);
Point% p4 = *hp;
Point% p5 = *gcnew Point(2,5);
在此,hp是一個Point的句柄,而p4是此句柄的別名。雖然句柄不是一個指針,但也能使用一元 * 操作符來對句柄解引用。(在C++/CLI標(biāo)準(zhǔn)制定期間,是否就引入一元 ^ 操作符來取代 * 還進(jìn)行過一場討論,反方觀點(diǎn)是,在編寫模板時,* 對句柄或指針進(jìn)行解引用有非常高的價值。)當(dāng)然,即使hp有了一個新值,p4在此仍是同一Point的別名。另外要說明一點(diǎn),當(dāng)對象有一個句柄或跟蹤引用時,就不能被垃圾回收器回收了。
再來看p5,對gcnew返回的句柄進(jìn)行了解引用,雖然差不多每個引用類類型的句柄,都能被解引用,但有兩種類型的句柄卻不能被解引用,這兩種類型是:System::String與array。
取句柄操作符
如果想把p1的值寫到標(biāo)準(zhǔn)輸出,代碼似乎應(yīng)該像下面這樣:
Console::WriteLine("p1 is {0}", p1);
然而,這卻不能通過編譯,因?yàn)閃riteLine沒有一個可接受Point的重載版本。前面也提過,任何值類型的表達(dá)式(如int、long、double)會由一個"裝箱"的過程,自動轉(zhuǎn)換為Object^。雖然p1看上去比較像一個值類型的實(shí)例,但它實(shí)際上卻不是,它是一個引用類的實(shí)例,所以代碼需要這樣修改: Console::WriteLine("p1 is {0}", %p1);
通過使用一元 % 操作符,我們創(chuàng)建了對象p1的一個句柄,因?yàn)槊總€引用類最終都是從System::Object繼承的,而WriteLine也有一個其第二個參數(shù)可接受Object^的重載版本,所以,%p1的Point^就轉(zhuǎn)換為Object^,并顯示出p1相應(yīng)的值。要留意的是,此處沒有裝箱,但這個操作符不能應(yīng)用到本地類的實(shí)例上。
GC-Lvalues
在C++標(biāo)準(zhǔn)中定義及使用了lvalue術(shù)語,而C++/CLI標(biāo)準(zhǔn)則添加了gc-lvalue術(shù)語,其指"一個引用CLI堆中對象、或包含此對象的數(shù)值成員的表達(dá)式"。如果有一個指向gc-lvalue的句柄,可對其使用一元 * 操作符來產(chǎn)生一個gc-lvalue;而跟蹤引用也是一個gc-lvalue,當(dāng)%h中h是一個句柄時,它也可以產(chǎn)生一個gc-lvalue。(因?yàn)橛袕膌value至gc-lvalue的標(biāo)準(zhǔn)轉(zhuǎn)換,所以一個跟蹤引用可綁定至任意的gc-lvalue或lvalue。)
現(xiàn)在回顧一下以前定義的Point引用類,再來看一下以下變量定義:
Point p1, p2(3,4);
從本地C++的角度來說,p1與p2應(yīng)為基于堆棧的引用類Point實(shí)例,哪怕是從一般性的角度來看,它們也是。P1由默認(rèn)的構(gòu)造函數(shù)初始化,而p2由接受x與y坐標(biāo)的構(gòu)造函數(shù)初始化。從實(shí)現(xiàn)上來看,Point是自包含類型的(也就是說,它不包含任何指針或句柄),然而,作為一個引用類的實(shí)例,它仍處于CLI運(yùn)行時的掌控之下,且在必要時,會被垃圾回收--正因?yàn)榇?,所以不能定義一個引用類的靜態(tài)或全局實(shí)例。
同時,也不能將sizeof應(yīng)用于指明是引用類實(shí)例的表達(dá)式,因?yàn)閟izeof是在編譯時進(jìn)行計算的,而Point對象的大小要直到運(yùn)行時才能確定;但是,可將sizeof應(yīng)用于句柄,因?yàn)樗拇笮≡诰幾g時就已經(jīng)確定了。
另外,還不能定義一個基于堆棧的CLI數(shù)組實(shí)例。
跟蹤引用
本地C++可通過&來定義一個對象的別名,例如,對任意本地類N,可編寫如下代碼:
N n1;
N& n2 = n1;
引用必須在定義時進(jìn)行初始化,且在整個生命期中,它們都鎖定于引用同一對象,也就是說,它的值不會改變。引用一個引用類的實(shí)例與引用一個本地類基本一致,只不過語法不同而已。
在程序執(zhí)行期間,引用類的實(shí)例會在內(nèi)存中"移動",所以,需要對它們進(jìn)行跟蹤,而本地指針與引用卻不能夠勝任這項(xiàng)工作(尤其指不能對一個引用類的實(shí)例使用取地址符&),因此,C++/CLI對應(yīng)地提供了句柄及用于跟蹤的引用--在此簡稱為跟蹤引用(Tracking References),例如,你可以定義一個跟蹤引用p3,以追蹤對象p2:
Point% p3 = p2;
跟蹤引用的內(nèi)存存儲方式必須為自動(atuomatic),另外,盡管本地對象不會在內(nèi)存中"移動",但在上面的n2中,不能使用%來代替&。在C++/CLI中,%之于^,就如同本地C++中的&之于*。
請看下列代碼:
Point^ hp = gcnew Point(2,5);
Point% p4 = *hp;
Point% p5 = *gcnew Point(2,5);
在此,hp是一個Point的句柄,而p4是此句柄的別名。雖然句柄不是一個指針,但也能使用一元 * 操作符來對句柄解引用。(在C++/CLI標(biāo)準(zhǔn)制定期間,是否就引入一元 ^ 操作符來取代 * 還進(jìn)行過一場討論,反方觀點(diǎn)是,在編寫模板時,* 對句柄或指針進(jìn)行解引用有非常高的價值。)當(dāng)然,即使hp有了一個新值,p4在此仍是同一Point的別名。另外要說明一點(diǎn),當(dāng)對象有一個句柄或跟蹤引用時,就不能被垃圾回收器回收了。
再來看p5,對gcnew返回的句柄進(jìn)行了解引用,雖然差不多每個引用類類型的句柄,都能被解引用,但有兩種類型的句柄卻不能被解引用,這兩種類型是:System::String與array。
取句柄操作符
如果想把p1的值寫到標(biāo)準(zhǔn)輸出,代碼似乎應(yīng)該像下面這樣:
Console::WriteLine("p1 is {0}", p1);
然而,這卻不能通過編譯,因?yàn)閃riteLine沒有一個可接受Point的重載版本。前面也提過,任何值類型的表達(dá)式(如int、long、double)會由一個"裝箱"的過程,自動轉(zhuǎn)換為Object^。雖然p1看上去比較像一個值類型的實(shí)例,但它實(shí)際上卻不是,它是一個引用類的實(shí)例,所以代碼需要這樣修改: Console::WriteLine("p1 is {0}", %p1);
通過使用一元 % 操作符,我們創(chuàng)建了對象p1的一個句柄,因?yàn)槊總€引用類最終都是從System::Object繼承的,而WriteLine也有一個其第二個參數(shù)可接受Object^的重載版本,所以,%p1的Point^就轉(zhuǎn)換為Object^,并顯示出p1相應(yīng)的值。要留意的是,此處沒有裝箱,但這個操作符不能應(yīng)用到本地類的實(shí)例上。
GC-Lvalues
在C++標(biāo)準(zhǔn)中定義及使用了lvalue術(shù)語,而C++/CLI標(biāo)準(zhǔn)則添加了gc-lvalue術(shù)語,其指"一個引用CLI堆中對象、或包含此對象的數(shù)值成員的表達(dá)式"。如果有一個指向gc-lvalue的句柄,可對其使用一元 * 操作符來產(chǎn)生一個gc-lvalue;而跟蹤引用也是一個gc-lvalue,當(dāng)%h中h是一個句柄時,它也可以產(chǎn)生一個gc-lvalue。(因?yàn)橛袕膌value至gc-lvalue的標(biāo)準(zhǔn)轉(zhuǎn)換,所以一個跟蹤引用可綁定至任意的gc-lvalue或lvalue。)