一、模板類靜態數據成員的定義:
在下面的代碼中,我們給我一個基於模板的單實例類SingletonClass,同時在該類中給出獲取單實例和釋放單實例的兩個靜態方法。這樣,對於其他需要具有單實例功能的其他類直接繼承該類便可同樣具有了單實例的功能,該技巧可同樣應用於引用計數功能。在下面的例子中,我們在模板類中聲明了一個靜態成員來表示單實例對象,和普通類的靜態成員一樣,該靜態成員同樣需要在外部被定義,但是其定義的規則在語法上普通類稍有不同,這一點可以在下面的示例中體現出來。
1 #include <stdio.h> 2 3 template<typename T> 4 class SingletonClass { 5 public: 6 static T* GetInstance() { 7 if (NULL == _instance) 8 _instance = new T(); 9 return _instance; 10 } 11 static void ReleaseInstance() { 12 if (NULL != _instance) { 13 delete _instance; 14 _instance = NULL; 15 } 16 } 17 private: 18 static T* _instance; 19 }; 20 21 template<typename T> 22 T* SingletonClass<T>::_instance = NULL; 23 24 class MySingletonClass : public SingletonClass<MySingletonClass> { 25 public: 26 MySingletonClass() {} 27 ~MySingletonClass() {} 28 29 public: 30 void DoPrint() { 31 printf("This is MySingletonClass.\n"); 32 } 33 }; 34 35 int main() 36 { 37 MySingletonClass* myClass = MySingletonClass::GetInstance(); 38 myClass->DoPrint(); 39 MySingletonClass::ReleaseInstance(); 40 return 0; 41 }
二、虛成員函數:
模板類的成員模板函數不能是虛函數,因為虛函數調用機制的普遍實現都使用了一個大小固定的表,每個虛函數都對應表的一個入口。然而,成員模板函數的實例化個數則需要等到整個程序都編譯完畢后才能最終確定,即如果某個成員模板函數沒有被任何代碼調用過,那么該函數將不參與編譯,因此也就不會有對應的函數入口。鑒於此,這就和虛函數表需要固定大小的限制發生了沖突。然而需要明確指出的是,模板類中的普通成員函數是可以成為虛函數的,因為普通成員函數是隨同模板類一同被編譯的。見如下代碼示例:
1 #include <stdio.h> 2 3 template<typename T> 4 class TestClass { 5 public: 6 virtual ~TestClass() {} //析構函數是普通的成員函數,因此可以是虛函數。 7 8 public: 9 template<typename T2> 10 virtual void DoTest(T2 const& ) {} //vs2010給出的編譯錯誤提示是: 'member function templates cannot be virtual' 11 }; 12 13 int main() 14 { 15 TestClass<int> c; 16 return 0; 17 }
三、模板參數:
C++的模板參數可以為普通類型參數、非類型參數和模板類型的模板參數。對於普通類型參數而言,我們可以將其視為typedef定義的類型名稱。因此,該類型參數將不能用於某個類型的聲明,如:
template<typename T>
class TestClass {
... ...
friend class T; //這樣的聲明是錯誤的。
}
對於非類型參數而言,目前C++的標准僅僅支持整型、枚舉、指針類型和引用類型,如:
template<typename T, //該模板參數是普通類型參數。
typename T::InternalType* t> //非類型參數
class TestClass {
... ...
}
對於上例中的typename T::InternalType,即便也有typename關鍵字進行修飾,但是這里typename的函數和上一個是不同的,他用於標識InternalType是模板參數內部定義的類型,與此同時,他本是也是一個指針,因此我們需要將其視為非類型參數。對於非類型參數還需要明確說明的是,非類型參數只能是右值,即不能被取址,也不能被賦值。
對於模板的模板參數,其大多數聲明和使用方式都是和普通類型參數相同的,一個明顯的差別是,模板的模板參數的類型參數只能為其自身使用,而不能被其外部的模板類使用,見如下代碼和說明:
template<template<typename T> class Test>
class MyTest {
static T* t; //在這里,T是模板類MyTest類型參數Test的類型參數,因此MyTest中不能直接使用。
}
最后一點是關於缺省模板參數的。一個重要的約束是,缺省參數只能依賴於template-id列表中之前聲明的類型參數,而不能直接依賴當前類型參數,如:
template<typename T1, typename T2, typename T3 = DefaultT3<T1> >
class TestClass {
... ...
}
對於上述代碼中的缺省模板參數DefaultT3<T1>,其類型參數只能依賴於此之間聲明的T1和T2,而不能直接依賴其表示的T3。
四、模板實參:
C++編譯器實例化模板參數的方式主要有3種:顯示模板實參。缺省模板參數和動態推演實參。見如下示例代碼:
1 template<typename T> 2 T const& max(T const& a, T const& b) { 3 return a < b ? b : a; 4 } 5 int main() { 6 max<double>(1.0,2.0); //顯示指定模板參數。 7 max(1.0,2.0); //模板實參被隱式推演成double 8 max<int>(1.0,2.0); //顯示指定的模板參數,會將函數函數直接轉換為int。 9 }
對於動態推演方式,如果某個類型參數永遠不會被推演,那么我們可以考慮將其放在模板參數列表的前面,至於可以被推演出來的類型參數,我們應該盡可能的將其放在后面,從而我們在使用時只是顯示指定必須要指定的類型參數,而其余參數則可以通過推演的方式被實例化。如:
template<typename ReturnType, typename ParamType>
inline ReturnType Test(ParamType x) {
return x;
}
下面將再給出一個有關模板參數推演的實用技巧,即如果模板函數存在函數重載,那么該如何判別編譯器選擇的是哪個重載函數呢?
1 #include <stdio.h> 2 3 template<typename T> 4 int Test(T v) { 5 return v; 6 } 7 8 template<typename T> 9 double Test(T v, T v2) { 10 return v; 11 } 12 13 int main() { 14 //通過判斷返回值的長度來確定調用的是哪個重載函數。 15 printf("This size of return value is %d.\n",sizeof(Test(0,0))); 16 printf("This size of return value is %d.\n",sizeof(Test(0))); 17 return 0; 18 }
模板的參數在實例化的過程中並不會考慮從派生類到基類的轉化,見如下代碼:
1 #include <stdio.h> 2 3 class Base { 4 public: 5 virtual void DoTest() { 6 printf("This is Base::DoTest()\n"); 7 } 8 }; 9 10 class Derive : public Base { 11 public: 12 void DoTest() { 13 printf("This is Derive::DoTest()\n"); 14 } 15 }; 16 17 template<typename T, T* param> 18 class TestClass { 19 public: 20 void DoTest() { 21 param->DoTest(); 22 } 23 }; 24 25 Derive d; 26 int main() 27 { 28 TestClass<Base,&d> test; //這里T的類型為Base,而第二個非類型參數的類型為Derive了。 29 test.DoTest(); 30 return 0; 31 }
在編譯以上代碼時,vs2010將給出的編譯錯誤為:'specialization' : cannot convert from 'Derive *' to 'Base *'
五、友元:
關於友元本身的概念這里就不再做過多的贅述了,而只是介紹一下在模板類中的友元和普通類中友元在語法規則上的差異。
1. 如果要把模板聲明的實例聲明為其他類的友元時,該模板類必須已經被聲明了,而普通類沒有這樣的限制。如:
1 template<typename T> 2 class TemplateFriendClass { 3 ... ... 4 } 5 6 template<typename T> 7 class TestTest { 8 friend class NormalFriendClass; //在此之前,普通類NormalFriendClass並沒有被提前聲明。 9 friend class TemplateFriendClass<T>; //模板類TemplateFriendClass在此之前必須已經被聲明過了。 10 ... ... 11 }
2. 該差異是由上一條引申而來,即如果是友元模板函數,即便該模板函數已經被提前聲明了,那么在定義友元時,實例化的友元函數一旦不能被推演並匹配之前聲明的模板函數,編譯器仍然會報錯。因為友元模板函數不能在此處被定義。見如下代碼:
1 template<typename T1,typename T2> 2 void TestFunc(T1,T2); 3 4 class TestClass { 5 friend void TestFunc<>(int&,int&); //OK. 該函數可以推演為與上面匹配的TestFunc。 6 friend void TestFunc<int,double>(int,double); //OK. 同樣匹配上面聲明的模板函數。 7 friend void TestFunc<char>(char&,int); //錯誤,該函數不能匹配上面聲明的模板函數,這里就將表示新函數的聲明了。 8 friend void TestFunc(int,int) {} //錯誤,此處不能進行函數的定義。 9 }
3. 模板類中聲明友元函數時,應注意以下場景:
1 template<typename T> 2 class TestClass { 3 friend void TestFunc() { 4 ... ... 5 } 6 }; 7 int main() { 8 TestClass<void> t1; //此處TestClass模板類第一次被實例化,也同樣產生了TestFunc函數的定義。 9 TestClass<double> t2; //基於double類型再次實例化TestClass,而也將再次實例化TestFunc函數。 10 return 0; 11 }
從上例中可以看出,在兩次定義模板類變量時,TestFunc函數會跟隨模板類TestClass基於不同類型的實例化而被定義兩次。這將導致編譯錯誤。下面將給出一種正確的方式。
template<typename T>
class TestClass {
friend void TestFunct(T*) {
... ...
}
};
修訂后的代碼中,TestFunc函數仍為普通函數,而非模板函數,只是他的函數參數類型依賴了外部類的模板參數,因此該函數可以在此處被定義。又由於函數參數的類型不同,其定義的符號也是不同的,因此即便多次實例化模板類TestFunc,友元函數TestFunc也會基於不同的類型參數來定義該函數的。
4. 如果聲明友元模板,那么該模板將只是基本模板,然而在聲明之后,與該基本模板想對應的特化模板和部分特化模板也將自動的被看成友元了。見如下代碼示例和關鍵性注釋:
1 #include <stdio.h> 2 3 class TestClass { 4 private: 5 static void DoPrint() { 6 printf("This is a private function of class TestClass.\n"); 7 } 8 //這里聲明的友元類FriendClass是一個基本模板類。 9 template<typename T> friend class FriendClass; 10 }; 11 12 //很顯然,基本模板類FriendClass中DoTest方法是可以訪問TestClass中的私有方法的。 13 template<typename T> 14 class FriendClass { 15 public: 16 static void DoTest() { 17 TestClass::DoPrint(); 18 } 19 }; 20 //這里FriendClass是基本模板類FriendClass的一個特化類,因此他的DoTest函數也可以 21 //TestClass中的私有方法DoPrint()。 22 template<> 23 class FriendClass<double> { 24 public: 25 static void DoTest() { 26 TestClass::DoPrint(); 27 } 28 }; 29 30 int main() 31 { 32 FriendClass<int>::DoTest(); 33 FriendClass<double>::DoTest(); 34 return 0; 35 }
