再論類型
在討論裝箱(boxing)之前,有必要弄清楚為什麼值類型與引用類型之間會有所區別。
一個含有數值的值類型的實例,和一個指向對象的引用類型的實例,它們有什麼區別呢?除了存儲對象所需的內存之外,每一個對象都會有一個對象頭,目的是為面向對象的編程提供基本的服務,如存在虛方法的類,嵌入其中的元數據等等。由虛方法和接口間接結合的對象頭,其內存開銷通常會很大,哪怕你所需要的只是一個靜態類型的數值,也會帶來一些編譯器的強制操作。有趣的是,在某些情況下,編譯器能優化掉一些對象開銷,但不總是能起作用。如果你非常在意托管代碼的執行效率,那麼使用數值或值類型將會有所益處,但在本地C++的類型中,這不算一個很大的區別,當然,C++也沒有強制任何編程范式,所以也有可能在C++之上,通過創建庫來建立一個這樣截然不同的類型系統。
裝箱
什麼是裝箱(boxing)?裝箱是一種用來橋接數值和對象的機制。盡管CLR的每種類型都是直接或間接從Object類派生而來,但數值卻不是。一個堆棧上的數值(如整形int),只不過是一個編譯器會進行某種特定操作的內存塊。如果你實在想把一個數值當成一個對象,必須對數值調用從Object繼承而來的方法,為了實現這一點,CLR提供了裝箱的概念。知道一點裝箱的原理還是有點用的,首先,一個數值通過使用ldloc IL指令入棧,接下來,裝箱IL指令運行,把數值類型提升,CLR再把數值出棧,並分配足夠的空間存儲數值與對象頭,然後一個對新建對象的引用被壓入棧,所有這些就是裝箱指令要做的事。最後,為取得對象引用,stloc IL指令從堆棧中彈出引用,並把它存儲在局部變量中。
現在,問題是:在編程語言中,對數值的裝箱操作,是應該表現為隱式還是顯式呢?換句話說,是否應使用一個顯式轉換或其他構造函數呢?C#語言設計者決定做成隱式轉換,畢竟,一個整形數是從Object間接繼承來的Int32類型。
int i = 123;
object o = i;
問題來了,正如我們所知,裝箱不是一個簡單的向上轉換,它有點像把一個數值轉換成一個對象,是一個存在潛在代價的操作。正是因為這個原因,托管C++通過使用關鍵字__box,來進行顯式裝箱。
int i = 123;
Object* o = __box(i);
當然,在托管C++中,當裝箱一個數值時,不會失去靜態類型信息,而這一點,正是C#所缺乏的。
int i = 123;
int __gc* o = __box(i);
指定強類型的裝箱值有利於再次轉換回到一個數值類型,或被稱為解箱(unboxing),不使用dynamic_cast,只是簡單地解引用一個對象。
int c = *o;
當然,托管C++的顯式裝箱所帶來的句法上的花銷,在許多情況下已被證明是巨大的。正因為此,改變了C++/CLI語言的設計過程,成了與C#保持一致--隱式裝箱。在相同情況下,它在直接表示強類型裝箱數值上保持了類型安全,而這正是其他 .NET語言所做不到的。
int i = 123;
int^ hi = i;
int c = *hi;
hi = nullptr;
在此,也暗示著一個沒有指向任何對象的句柄,不能被初始化為零,在這一點上,與指針是一致的,因為這將導致只是把數值"零"裝箱;同時這也是常量nullptr存在的原因,它能被賦給任何句柄,且是C#中關鍵字null的對等物。盡管在C++/CLI語言設計中,nullptr是一個新的保留字,但它已被Herb Sutter和Bjarne Stroustrup提議增加在標准C++中。
編寫引用和值類型
在C#中,通常用關鍵字class來聲明一個引用類型,而用關鍵字struct來聲明值類型:
class ReferenceType {}
struct ValueType {}
對於class和struct,C++已經有定義好了的含義,所以這在C++中行不通。在最初的語言設計上,放置在類前的關鍵字__gc表示這是一個引用類型,而關鍵字__value則表示這是一個值類型。
__gc class ReferenceType {};
__value class ValueType {};
C++/CLI在那些不會與用戶的其他標識符發生沖突的地方引入了"空隔"關鍵字。為了聲明一個引用類型,只需在class或struct之前加上ref,類似地,可用value來聲明值類型。
ref class ReferenceType {};
ref struct ReferenceType {};
value class ValueType {};
value struct ValueType {};
關於使用class還是struct,與默認狀態下類成員的可見度有關,在CLR中,最大的不同之處在於,只支持公有繼承。使用private(私有)或protected(保護)繼承都將會導致編譯錯誤,因此,顯式聲明公有繼承是合法但卻多余的。