相信大家對模板並不陌生,模板的基本概念我想就不用多說了。大多數人包括我自己對模板的理解就是“T容器”。
請看下面的代碼:
template<int m1, int l1, int t1, int m2, int l2, int t2>
Physical<m1+m2, l1+l2, t1+t2> operator *( Physical<m1, l1, t1 > lhs, Physical<m2, l2, t2> rhs)
{
return Physical<m1+m2, l1+l2, t1+t2>::unit * lhs.value() * rhs.value();
}
頭一次見到template的這種用法時,我確實有點目眩。看來我們對模板的認識只是皮毛而已 J
在當今的程序設計中模板已經顯露出它的價值來了,比如STL全部都是以模板架構的。自VS2003(VC7)以來的編譯器也對模板提供了更多的支持。
我們需要對模板有更進一步的認識,下面就讓我們進入模板的世界。
- 模板的特性
首先來看一個例子:
template<int n>
int Func1() { return n; }
int Func2(int n) { return n; }
你能看出這兩個函數的區別在哪裏嗎?
它們的區別就在於,Func1的參數是編譯期指定,如Func1<0>() ;而Func2的參數則是運行期指定。
這正體現了模板的特性或者說是它的技術核心,就是編譯期的動態機制,這種機制使程序在運行期具有更大的效率優勢。
到此你是不是對本文開頭那段代碼有了更深入的理解了呢?
模板的另一個特性是,如果一個模板沒有被特化,那麼編譯器根本不會去理會它,也就是說模板內的代碼被隱藏了。
- 函數模板與類模板
這是一個函數模板:template<class T> Func(T param) {}
函數模板的模板參數是隱式的,編譯器會自動根據傳入值的類型來確定模板參數的類型。因此函數模板的模板參數不能有默認值。
這是一個類模板:template<class T> class MyClass {};
類模板的模板參數是顯式的,使用一個模板類時必須指明其模板參數,因此類模板的模板參數可以有默認值。
我們還可以做更多的事情,比如MyClass可以派生自T(XTP界面庫就是這麼做的),在MyClass內部可以使用關於T的enum、typedef等等。這些將在下文一一談到。
(我在這裏提出一個建議,創建一個類模板,請記得第一件事就是對模板參數進行typedef定義。)
- 模板的部分特化與應用
模板最有價值的地方就是它的部分特化,也是應用最廣泛的特性。
所謂“部分特化”也就是說,一個模板有多個參數,但我們只對其中一部分參數進行特化,或者是隻針對常量模板參數的某種情況進行特化。
編譯期ASSERT
這是對bool型模板參數部分特化的一個例子。最簡單的實現:
template<bool> struct CompileTimeAssert;
template<> struct CompileTimeAssert<true> {};
當我們將一個表達式作爲模板參數,而這個表達式的值爲false時,編譯器就找不到合適的實現,便會報錯了。
我們可以將它擴展一下:
template<bool> struct CompileTimeAssert { CompileTimeAssert(…) ;}; // 這裏使用了C++支持的任意參數表
template<> struct CompileTimeAssert<false> {};
#define COMPILE_CHECK(expr, msg) /
{/
class ERROR_##msg {}; /
(void) CompileTimeAssert<((expr)!=0)>(ERROR_##msg()); /
}
// 這裏不直接傳入expr是爲了獲得更大的適應性,因爲expr有可能是無法隱式轉換爲bool型的(比如指針),筆者曾參與的
// 一個項目在由VC6平臺升級到VS2003平臺時,由於後者ASSERT宏實現的變化,就遇到了這一問題。
當然,事實是編譯期可用的表達式或函數(如sizeof、__alignof)數量上並不太多,但是這種方式是有着積極意義的。
編譯期分派與類型選擇
常量映射爲類型
請看這樣一個模板:template<int v> struct Int2Type{ enum{ value = v }; };
模板參數的不同數值,就會產生不同類型的Int2Type。即Int2Type<0>不同於Int2Type<1>,以此類推。
我們可以利用這個模板實現編譯期分派。看這樣一個例子:
template<class T, bool bPolymorphic>
class MyClass
{
……
void Func(T* pObj)
{
if (bPolymorphic)
{
T* pNewObj = pObj->Clone(); // 多態類型
……
}
else
{
T* pNewObj = new T(*pObj); // 非多態類型
……
}
}
};
顯然,編譯器不會讓你僥倖成功。而使用了