一、typename的另一種使用方式:
在此之前,我們了解到的有關該關鍵字的用途更多是針對模板參數的定義。而這里介紹的使用方式則有些不同,主要區別是typename的這種使用方式用於定義或提示編譯器,其后修飾的標識符為模板參數中的類型標識符,而不是普通的靜態類成員。見以下代碼示例和關鍵性注釋。
1 #include <stdio.h> 2 3 template<typename T> 4 class MyTestClass { 5 public: 6 //這里的typename是用於通知編譯器,其后的MyType是模板T定義的內部類型,從這個代碼示例 7 //中可以看出,不管是在函數參數、返回值還是定義成員變量定義,都要遵守這一語法規則。 8 MyTestClass(typename T::MyType p) : _p(p) { 9 } 10 typename T::MyType GetData() const { 11 return _p; 12 } 13 public: 14 typename T::MyType _p; 15 }; 16 //在該類中,由於后面的調用需要將其用作MyTestClass的模參,因此他必須typedef名字為MyType的 17 //內部類型,否則將會導致編譯失敗。 18 class TemplateParamClass { 19 public: 20 typedef const char* MyType; 21 }; 22 23 int main() { 24 MyTestClass<TemplateParamClass> t("Hello"); 25 printf("The data is %s.\n",t.GetData()); 26 return 0; 27 }
二、基於不同類型模參的模板類之間的賦值:
前面的博客已經提到過,編譯器會將不同類型實例化后的模板類視為不同的類型,因此在這種不同的類型之間進行賦值,如果沒有提供必要的方法是無法正常編譯的,如:
1 template <typename T> 2 class TestClass { 3 ... ... 4 } 5 int main() { 6 TestClass<int> intClass; 7 TestClass<double> doubleClass = intClass; 8 }
上例中的代碼是無法成功編譯的,因為在C++編譯器看來,TestClass<int>和TestClass<double>就是兩個完全不同的類型,這樣也就無法利用C++編譯器自動生成的缺省賦值函數完全這一賦值功能。如何修改才可以達到這一效果呢,見如下代碼示例和關鍵性注釋。
1 #include <stdio.h> 2 3 template <typename T> 4 class TestClass { 5 public: 6 TestClass(T v) : _value(v) { 7 } 8 template<typename T2> 9 TestClass<T>& operator= (const TestClass<T2>& other) { 10 //這是編寫重載賦值操作符的基本技巧,既不能進行自身對自身的賦值。 11 if ((void*)this == (void*)&other) 12 return *this; 13 //需要說明的是,由於other和this是通過不同類型實例化的模板類,因此這里不能 14 //像普通類那樣直接調用other._value。編譯器將他們視為不同的類型,所以也就不 15 //能直接訪問其私有變量了。 16 _value = other.getValue(); 17 return *this; 18 } 19 T getValue() const { 20 return _value; 21 } 22 private: 23 T _value; 24 }; 25 26 int main() { 27 TestClass<int> intClass(5); 28 TestClass<double> doubleClass(6.4); 29 printf("The value before assignment is %f.\n",doubleClass.getValue()); 30 //此時的T為double,T2為int。 31 doubleClass = intClass; 32 printf("The value after assignment is %f.\n",doubleClass.getValue()); 33 return 0; 34 } 35 //The value before assignment is 6.400000. 36 //The value after assignment is 5.000000.
三、模板的模板參數:
所謂的模板的模板參數,是指模板的參數是另外一個模板類。如:
template<typename T>
class TestClass {
... ...
}
對於上面的模板類,其模參類型並沒有任何限制,即可以為任意類型,如普通原始類型、類類型,模板類等。而對於要求模參必須要模板類類型的模板類而言,其模參必須是模板類,否則將無法通過編譯。如:
template <template <typename T> class TemplateClass >
class TemplateTemplateClass {
... ...
}
對於上面的模板類,其模參必須是符合template<typename T> class簽名的模板類,該類只有一個普通模參。見如下示例代碼和關鍵性注釋。
1 #include <stdio.h> 2 //該模板類的第二個模板必須是另外一個模板類。 3 template<typename T, template <typename T> class TemplateClass> 4 class TemplateTemplateClass { 5 public: 6 TemplateTemplateClass(const TemplateClass<T>& tempClass) 7 : _internalClass(tempClass) { 8 } 9 void doTest() { 10 _internalClass.doTest(); 11 } 12 private: 13 TemplateClass<T> _internalClass; 14 }; 15 16 template <typename T> 17 class MyTemplateClass { 18 public: 19 void doTest() { 20 printf("This is MyTemplateClass::doTest().\n"); 21 } 22 }; 23 24 int main() { 25 MyTemplateClass<int> tempClass; 26 TemplateTemplateClass<int,MyTemplateClass> tempTempClass(tempClass); 27 tempTempClass.doTest(); 28 return 0; 29 } 30 //This is MyTemplateClass::doTest().
四、零初始化:
對於成員變量而言,我們通常的做法是在類構造函數的初始化列表中完成這些成員變量的初始化工作,以免在使用這些變量時造成一些不必要的Bug。對於模板參數類型的成員變量我們應該如何處理呢?見如下示例代碼和關鍵性注釋:
1 #include <stdio.h> 2 3 template <typename T> 4 class TestClass { 5 public: 6 //1. 第一步是為模板類提供一個缺省構造函數,在該函數的初始化列表中顯示的完成 7 //模參類型成員的變量的初始化。 8 //2. 在這個例子中,要求模板類型或者為原始類型,如int、double等。倘若是類類型 9 //則該類必須提供缺省構造函數,否則將導致編譯失敗。 10 //3. 對於原始類型,如int,在執行完_value()之后,其值將被初始化為0。 11 TestClass() : _value() { 12 } 13 private: 14 T _value; 15 }; 16 int main() { 17 TestClass<int> t; 18 return 0; 19 }
五、使用字符串作為模板函數的實參:
見如下代碼:
1 template<typename T> 2 inline T const& max(T const& a, T const& b) { 3 return a < b ? b : a; 4 }
如果上述函數的模參類型為char*,那么函數在執行時將會直接使用指針的地址值直接進行比較,而不是按照一個常規方式,按照指針所指向的字符串數據進行lexical方式的比較。
對於上述函數還存在一個比較隱式的問題,即如果函數的參數不是char*而是字符數組的話,由於max的參數為T const&引用類型,因此在模板函數實例化時將會造成很多限制,見如下代碼示例:
1 #include <stdio.h> 2 #include <typeinfo> 3 4 template <typename T> 5 void ref(T const& x) { 6 printf("x in ref(T const&): %s.\n",typeid(x).name()); 7 } 8 9 template <typename T> 10 void nonref(T x) { 11 printf("x in nonref(T): %s.\n",typeid(x).name()); 12 } 13 14 int main() { 15 ref("Hello"); 16 nonref("Hello"); 17 return 0; 18 } 19 //x in ref(T const&): char const [6]. 20 //x in nonref(T): char const *.
從上述輸出結果可以看出,使用引用作為函數參數時,編譯器會將模板實參推演為字符串數組,而非引用方式則不會。由於可以得出:
1 int main() { 2 //可以編譯通過,因為他們都是char const [6] 3 max("apple","peach"); 4 //不可以編譯通過,因為這兩個函數參數的類型分別為char const[6]和char const [5] 5 //對於這種情況,我們可以考慮重載一個max函數,其函數參數為T,而不是T const& 6 max("apple","pear"); 7 return 0; 8 }
