Visual Studio 2005把泛型編程的類型參數模型引入了微軟.NET框架組件。C++/CLI支持兩種類型參數機制--通用語言運行時(CLR)泛型和C++模板。本文將介紹兩者之間的一些區別--特別是參數列表和類型約束模型之間的區別。
參數列表又回來了
參數列表與函數的信號(signature)類似:它標明了參數的數量和每個參數的類型,並把給每個參數關聯一個唯一的標識符,這樣在模板定義的內部,每個參數就可以被唯一地引用。
參數在模板或泛型的定義中起占位符(placeholder)的作用。用戶通過提供綁定到參數的實際值來建立對象實例。參數化類型的實例化並非簡單的文本替代(宏擴展機制就是使用文本替代的)。相反地,它把實際的用戶值綁定到定義中的相關的形式參數上。
在泛型中,每個參數都表現為Object類型或衍生自Object的類型。在本文後面你可以看到,這約束了你可能執行的操作類型或通過類型參數聲明的對象。你可以通過提供更加明確的約束來調整這些約束關系。這些明確的約束引用那些衍生出實際類型參數的基類或接口集合。
模板除了支持類型參數之外,還支持表達式和模板參數。此外,模板還支持默認的參數值。這些都是按照位置而不是名稱來分解的。在兩種機制之下,類型參數都是與類或類型名稱關鍵字一起引入的。
參數列表的額外的模板功能
模板作為類型參數的補充,允許兩種類型的參數:非類型(non-type)參數和模板參數。我們將分別簡短地介紹一下。
非類型參數受常數表達式的約束。我們應該立即想到它是數值型或字符串常量。例如,如果選擇提供固定大小的堆棧,你就可能同時指定一個非類型的大小參數和元素類型參數,這樣就可以同時按照元素類別和大小來劃分堆棧實例的類別。例如,你可以在代碼1中看到帶有非類型參數的固定大小的堆棧。
代碼1:帶有非類型固定大小的堆棧
template <class elemType, int size>
public ref class tStack
{
array<elemType> ^m_stack;
int top;
public:
tStack() : top( 0 )
{ m_stack = gcnew array<elemType>( size ); }
};
此外,如果模板類設計者可以為每個參數指定默認值,使用起來就可能方便多了。例如,把緩沖區的默認大小設置為1KB就是很好的。在模板機制下,可以給參數提供默認值,如下所示:
// 帶有默認值的模板聲明
template <class elemType, int size = 1024>
public ref class FixedSizeStack {};
用戶可以通過提供明確的第二個值來重載默認大小值:
// 最多128個字符串實例的堆棧
FixedSizeState<String^, 128> ^tbs = gcnew FixedSizeStack<String^, 128>;
否則,由於沒有提供第二個參數,它使用了相關的默認值,如下所示:
// 最多1024個字符串實例的堆棧
FixedSizeStack<String^> ^tbs = gcnew FixedSizeStack<String^>;
使用默認的參數值是標准模板庫(STL)的一個基本的設計特征。例如,下面的聲明就來自ISO-C++標准:
// ISO-C++名字空間std中的默認類型參數值示例
{
template <class T, class Container = deque<T> >
class queue;
template <class T, class Allocator = allocator<T> >
class vector;
// ...
}
你可以提供默認的元素類型,如下所示:
// 帶有默認的元素類型的模板聲明
template <class elemType=String^, int size=1024>
public ref class tStack {};
從設計的角度來說很難證明它的正確性,因為一般來說容器不會集中在在單個默認類型上。
指針也可以作為非類型參數,因為對象或函數的地址在編譯時就已知了,因此是一個常量表達式。例如,你可能希望為堆棧類提供第三個參數,這個參數指明遇到特定條件的時候使用的回調處理程序。明智地使用typedef可以大幅度簡化那些表面上看起來很復雜的聲明,如下所示:
typedef void (*handler)( ... array<Object^>^ );
template <class elemType, int size, handler cback >
public ref class tStack {};
當然,你可以為處理程序提供默認值--在這個例子中,是一個已有的方法的地址。例如,下面的緩沖區聲明就提供了大小和處理程序:
void defaultHandler( ... array<Object^>^ ){ ... }
template < class elemType,
int size = 1024,
handler cback = &defaultHandler >
public ref class tStack {};
由於默認值的位置次序優先於命名次序,因此如果不提供明確的大小值(即使這個大小與默認值是重復的),就無法提供重載的處理程序的。下面就是可能用到的修改堆棧的方法:
void demonstration()
{
// 默認的大小和處理程序
tStack<String^> ^ts1 = nullptr;
// 默認的處理程序
tStack<String^, 128> ^ts2 = gcnew tStack<String^, 128>;
// 重載所有的三個參數
tStack<String^, 512, &yourHandler> ^ts3;
}
模板支持的第二種額外的參數就是template模板參數--也就是這個模板參數本身表現為一個模板。例如:
// template模板參數
template <template <class T> class arena, class arenaType>
class Editor {
arena<arenaType> m_arena;
// ...
};
Editor模板類列出了兩個模板參數arena和arenaType。ArenaType是一個模板類型參數;你可以傳遞整型、字符串型、自定義類型等等。Arena是一個template模板參數。帶有單個模板類型參數的任何模板類都可以綁定到arena。m_arena是一個綁定到arenaType模板類型參數的模板類實例。例如:
// 模板緩沖區類
template <class elemType>
public ref class tBuffer {};
void f()
{
Editor<tBuffer,String^> ^textEditor;
Editor<tBuffer,char> ^blitEditor;
// ...
}