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

