Visual Studio 2005體驗泛型編程
Visual Studio 2005 為 Microsoft .NET 框架帶來了泛型編程的類型參數化模型。當然,類型參數化是C++ 程序員 的事情。所以,對于那些還不熟悉它們的人,我將在本文中對泛型編程做一個簡要的介紹。 泛型編程的基本思想是交付固定的代碼庫,這個代碼庫支持潛在
Visual Studio 2005 為 Microsoft
.NET 框架帶來了泛型
編程的類型參數化模型。當然,類型參數化是
C++程序員的事情。所以,對于那些還不熟悉它們的人,我將在本文中對泛型編程做一個簡要的介紹。
泛型編程的基本思想是交付固定的代碼庫,這個代碼庫支持潛在的無限類型集合。有兩種用于泛型編程的常規模型:通用類型容器模型(Universal Type Container Model,UTCM)和類型參數化模型(Type Parameter Model,TPM)。
在 UTCM 中,與對象相關的類型信息已經被剝離。因為它是可還原的,所以容易實現。所有的對象都以一種統一的、非透明的方式存儲。而在一個像 CTS(Common Type System)這樣單一的類型體系中,通用類型容器就是即 Object;所有的 CTS 類型均直接或間接地的從 Object 中派生。比如說 C 語言中,void * 就是 通用類型。
在 TPM 中,與對象關聯的類型信息的綁定已經被提煉和延遲。從一個調用到另一種調用中,值的變化多種多樣,它們被提煉為參數。這就是為什么這種實現模型被稱為參數化 類型的原因——它更復雜,但功能強大。
例如,你在實現一個 System::Collections 名字空間的 IEnumerator 接口。只要提供兩個方法和一個屬性,好像很簡單。但是在強類型語言中,提供對所有用戶都可用的單一接口其難度是無法想象的。其難就難在我們的實現中 無法再使用“???”;
clearcase/" target="_blank" >cc66" width="90%" align="center" bgcolor="#dadacf" border="1">
interface class IEnumerator
{
property ??? Current { ??? get(); }
bool MoveNext();
void Reset();
}; |
類型系統需要你靜態標識與屬性的存儲器回填相關的類型以及獲取存取器(accessor)返回類型,但這當然是不可能的。用戶需要枚舉的潛在類型無以計數。你怎么辦?
簡單一點的常規解決辦法是 UTCM,在這里對象被作為容器。
interface class IEnumerator
{
property Object^ Current { Object^ get(); }
bool MoveNext();
void Reset();
}; |
這樣提供了一定程度上的隔離。它允許用單一不變的代碼庫來支持潛在的無窮多的類型。并且對于被動存儲和引用類型對象的獲取,其工作表現不俗。
一旦你你需要象混凝土類型那樣來獲取和處理該對象,事情就變得有些不那么雅致了。這要求向下強制轉換為最初的對象類型。不幸的是,編譯器沒有必要的類型信息來保證 強制類型轉換的正確性,從而造成程序員得手工顯式向下轉換,如下例所示:
extern void f( Object^ anyTypeWorks );
Object^ o = "a string of all things";
// no downcast ... passive storage
f( o );
// downcast ... we need to manipulate
String^ s = safe_cast<String^>( o ); |
在實現集合時碰到的問題更多,因為無法靜態約束某個集合在通用類型容器模型下僅容納單一類型的對象。這只能從程序一級提供,而且稍顯復雜和易錯。此外,因為它是一個程序
解決方案,只能被用于運行時期。 你再次得不到編譯器的支持。
除了
安全性和復雜性之外,還涉及大規模存儲以及在通用類型容器模型下獲取值類型的
性能問題。借助類型參數化,這三個問題迎刃而解。
什么是參數化的類型?
類型參數模型提供了第二層隔離,消除了向下強制類型轉換和框入/框出操作,并允許編譯時同類容器元素類型沖突的降格(flagging)。它是一種兩步解決方案。在第一步中,將一個類型參數當作一個 實際類型的占位符,就像函數所做的那樣:
interface class IEnumerator
{
property typeParameter Current { typeParameter get(); }
bool MoveNext();
void Reset();
} |
在第二步,你告訴編譯器(程序的機器閱讀器),typeParameter 是一個占位而不是一個程序實體。這一步是通過叫做參數化列表的泛型署名來完成的。在C++/CLI中, 要么引入 generic 關鍵字以選擇公共語言運行時(CLR)泛型機制,要么引入 template 關鍵字以選擇使用 C++
模板類型機制。如 Figure 1 所示。
Figure 1 Indicating typeParameter
Generic Keyword
generic <typename typeParameter>
interface class IEnumerator
{
property typeParameter Current { typeParameter get(); }
...
};
Template Keyword
template <typename typeParameter>
interface class IEnumerator
{
property typeParameter Current { typeParameter get(); }
...
}; |
這樣便導致了一個類型無關的接口定義。之后,當某個類實現 IEnumerator 時,它必需提供一個實際的類型綁定到 typeParameter 占位 符。這是通過將括弧中實際的類型與參數化類名配對實現的,比如IEnumerator<int>
C++/CLI 支持兩種參數化類型機制,模板和泛型,用于定義參數化引用、值和接口類,函數和委托。從表面上看,參數化的 generic 和 template 至少在語句構成上是等同的(除了 template 或 generic 關鍵字有所不同)。而在其它方面,它們有顯著的不同。
考慮一下 Figure 2 中的兩個棧聲明,template 實例(tStack),通過標準模板庫(STL)的 CLI 實現提供了一個使用動態 vector 容器的例子,以及 generic 實例(gStack),通過 System::Collections::Generic 名字空間提供的使用動態 List<T> 容器的例子。兩者在 Visual Studio 2005 中都是新的參數化類型集合庫。
Figure 2 Template and Generic Stack Declaration
C++/CLI Template Stack Declaration
#include <cliext/vector>
using namespace cliext;
template <class elemType>
ref class tStack
{
// the CLI vector is a reference class …
vector<elemType> ^m_stack;
int top;
public:
tStack();
elemType pop();
void push( elemType et );
...
};
C++/CLI Generic Stack Declaration
using namespace System::Collections::Generic
generic <class elemType>
public ref class gStack {
List<elemType> ^m_stack;
int top;
public:
gStack();
elemType pop();
void push( elemType et );
...
}; |
通過在類名后的尖括弧中指定實際類型來創建參數化類型實例。例如,Figure 3 依次示范了用整型和字符串類型參數實例化的 template 堆棧。為了創建等同的 generic 堆棧實例,Figure 4 所用的兩種類型參數是相同的。
Figure 3 Instantiating the Template Stack
void demo_template_Stack()
{
// an int value type argument...
tStack<int>^ is = gcnew tStack<int>( 10 );
for ( int ix = 0; ix < 10; ix++ )
is->push( ix*2 );
int elem_cnt = is->size();
for ( int ix = 0; ix < elem_cnt; ++ix )
Console::WriteLine( "({0}) {1}", ix+1, is->pop());
// a String^ reference type argument...
tStack<String^> ^ss = gcnew tStack<String^>( 10 );
ss->push( "Pooh" ); ss->push( "Piglet" );
ss->push( "Rabbit" ); ss->push( "Eeyore" );
elem_cnt = ss->size();
for ( int ix = 0; ix < elem_cnt; ++ix )
Console::WriteLine( "({0}) {1}", ix+1, ss->pop());
} |
Figure 4 Instantiating the Generic Stack
void demo_generic_Stack()
{
// an int value type argument...
gStack<int>^ is = gcnew gStack<int>( 10 );
for ( int ix = 0; ix < 10; ix++ )
is->push( ix*2 );
int elem_cnt = is->size();
for ( int ix = 0; ix < elem_cnt; ++ix )
Console::WriteLine( "({0}) {1}", ix+1, is->pop());
// a String^ reference type argument...
gStack<String^> ^ss = gcnew gStack<String^>( 10 );
ss->push( "Pooh" ); ss->push( "Piglet" );
ss->push( "Rabbit" ); ss->push( "Eeyore" );
elem_cnt = ss->size();
for ( int ix = 0; ix < elem_cnt; ++ix )
Console::WriteLine( "({0}) {1}", ix+1, ss->pop());
} |
參數化類型對象的實際處理,例如 is 和 ss,與非參數化類型對象的處理完全一樣。參數化類型的一個好處是單一的源定義能潛在地產生出無數種型實例。該例子中,generic 和 template 堆棧類在相同的參數化類源代碼之外都支持字符串和整型類。在所有已知類型的應用程序中使用它們時沒有真正的約束。正如你將會在后續專欄中看到的那樣,并不是所有參數化類型都這樣。
generic 和 template 定義以及種型實例在這里幾乎是等價的,盡管并不是所有的參數化類型都這樣?;蛘哒f在支持兩種機制的 C++/CLI 中益處不多。當我在后續專欄中詳細討論兩種機制時,你會看到其它一些差異。正是存在這些差異,在我遇到它們時,將它突出出來,而不是反復說其共性,似乎是整合其全貌的更好方法。
類型參數列表
每種類型參數都是以 class 或 typename 關鍵字開始的。這些關鍵字并包含任何平臺意義——例如,class 并不是暗示要是一個本地類型,typename 也不是 意味著就是公共語言基礎結構(CLI)類型。它們都表示緊跟著的名字是一個參數化類型的占位符,該占位符將會被用戶指定的類型參數所取代。
之所以用中兩個關鍵字是有歷史原因的。在最初的模板規范中,Stroust
rup 重用了現有的 class 關鍵字來指定一個類型參數而不是引入可能破壞已有程序的新關鍵字。直到 ISO-C++ 標準,class 關鍵字是聲明類型參數的唯一方法。
重用現有的關鍵字似乎總是容易產生混淆。使用 class 來表示類型參數(parameter)是不是比內建類型和指針類型更能限制可用類型參數(arguments)成為 class 類型呢?不是,那么在這種情況下使用 class 就不會使人誤解嗎?肯定會的。所以,有些人覺得不引入新的關鍵字會導致不必要的混亂。但是那不是引入 typename 關鍵字的原因。
事實上,將 typename 引入 C++ 的真正的原因是為了支持模板定義的解析。這是個比較深入的話題,我在此只做一點簡要介紹。詳細描述請參考 Stroustrup 的 《Design and Evolution of C++》(Addison-Wesley, 1994)。
在某些情況下,要編譯器來區分類型聲明和表達式是不可能的。如果編譯器遇到某個模板定義中的表達式Parm::name,并且 Parm 是一個表示 class 的模板類型參數,那么名稱是該叫類型成員還是 Parm 的數據成員呢?
template <class Parm, class U>
Parm minus( Parm* array, U value )
{
Parm::name * p; // Is this a pointer declaration or
// a multiplication expression?
// By default treated as expression.
} |
默認情況下,這個表示方法被認為是一個乘法表達式:運算符 Parm::name 乘以 p。關鍵字 typename 的引入使程序員能重寫這種默認的解釋。例如, 為了聲明 Parm::name 類型的指針 p,可將模板函數重寫如下:
template <class Parm, class U>
Parm minus( Parm* array, U value )
{
typename Parm::name * p; // ok: pointer declaration
} |
既然這個關鍵字的存在已經是一種既定的事實,那么非要消除因重用 class 關鍵字而導致的混亂是很不明智的作法。公布的代碼、書籍、文章、言論、
論壇和出版物 都廣泛使用它,因此不能對之視而不見。這就是為什么 C++ 對這兩個關鍵字都支持的原因。
關鍵字 class 或 typename 的后面是一個標識符,它在 template 或 generic 定義充當占位符。在參數列表中的每一個標識符必須唯一。但是,在 交叉聲明中這兩個關鍵字和標識符是可以改變的:
template <class T>
public ref class tStack;
// ok: both the keyword and identifier can vary across
// declarations of the same type
template <typename elemType>
public ref class tStack {}; |
標識符的作用域用于持續類型聲明的范圍。在 tStack 的前向聲明中,用分號結束,并且這個名字從沒有被引用過。在實際定義中, 不論是在類定義中,還是在該類的每個以非內聯(out-of-line)方式定義的成員函數中,這個標識符都是可見的。
類型實例化
template 或 generic 定義指明了當給定一個或多個實際類型集合時,如何構造單一的類或函數。實例化的時機是模板和泛型之間的一個主要區別之一。Template 的實例化是在編譯時完成的;而 generic 的實例化是 CLR 在運行時完成的(在后面 專欄中,我會作更詳細的介紹)。
template 定義做為一個自動產生特定類型實例的圖解;編譯器從字面上插入由用戶提供的特定類型參數。而 generic 定義則更像是個藍圖;在運行時 構造特定類型實例,根據類型參數是引用還是值類型來修改常規語法。例如,用如下的代碼,你可以從 template 和 generic 定義自動創建一個 int 類型的堆棧類對象和一個 String 類型的堆棧類對象: tStack<int> ^si;
tStack<String^> ^ss;
這個從模板定義中產生的類被稱為模板實例化——在 ISO-C++ 標準中就是這樣討論的。在泛型的文字描述中,類的生成被稱為構造——這 里又看到了模板與藍圖之間的不同之處。這里,我用“實例化”來描述這一過程。當 String 類型的堆棧類被實例化時,在 generic 或 template 定義中每每出現模板參數的地方都用 String 類型取代。該類型的正確性被驗證。
實例化的名稱是 Stack<int> 或 Stack<String^>。緊隨名字后面的 <int> 或 <String^> 符號在ISO-C++中被稱為模板參數。而在 generic 表述中 則稱為類型參數,本文我將遵循這樣的叫法。類型參數必須在用逗號分隔的列表中指定,并用尖括號括起來。實例化的名稱必須要顯式指定參數類型。與函數實例化類型參數不同,用于類實例化的類型參數決不能從所使用的類實例上下文來推斷——其含義將在未來關于參數化函數和函數類型的專欄中討論。
某個類的實例化可以在常規程序中任何使用非參數化類類型的地方使用,同樣,某個實例化后的類對象的聲明和使用與非參數化類完全相同。
最后,派生類和基類——綁定到獨立類型參數的兩個 generic(或 template)類型實例之間是沒有特別關系的。認識這一點很重要。比如說,你不能在沒有顯式編程操作的情況下,初始化或將一個賦值給另一個。即便對整型實例化對象的非公有成員具有存取許可,你也不能進行堆棧 String 實例化操作。
原文轉自:http://www.kjueaiud.com
- 評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
-
老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月
|