和函數(shù)一樣,C++中的class也可以類型參數(shù)化,其中容器類是極具這一特征的。對于模板類的基本定義和使用,可以參考STL,這里就不做過多的贅述了。下面將主要介紹一下與其相關的高級實用特征。
一、模板的特化:
這里可以先將類模板特化與面向對象中的多態(tài)進行一個簡單的比較,這樣可以便于我們對它的理解,也同樣有助于指導我們在實際的開發(fā)中應用這一C++技巧。眾所周知,對于多態(tài)而言,提供的是統(tǒng)一的接口和不同的實現(xiàn)類實例,其最終的行為將取決于實現(xiàn)類中的實現(xiàn),相信每一個有面向對象基礎的開發(fā)者對于這一概念并不陌生。而模板特化則要求必須提供一個標準的模板類(等同于多態(tài)中的接口),與此同時再為不同的類型提供不同的特化版本。在模板特化類中,我們首先需要保證類名是相同的,只是模板參數(shù)不再使用抽象的類型,而是直接使用具體的類型來實例化該模板類。在調用時,編譯器會根據(jù)調用時的類型參數(shù)自動選擇最為合適的模板類,即如果有特化模板類中的類型參數(shù)和當前調用類型相匹配的話,則首選該特化模板類,否則選擇最初的標準模板類。見如下用例(請注意代碼中的說明性注釋):
1 #include <stdio.h> 2 #include <string.h> 3 4 //1. 這里我們先聲明了一個通用類型的模板類。這里要有類型參數(shù)必須包含hashCode()方法。 5 //否則,該類型在編譯期實例化時將會導致編譯失敗。 6 template <typename T> 7 class CalcHashClass { //該類為標準模板類(等同于多態(tài)中的接口) 8 public: 9 CalcHashClass(T const& v) : _value(v) { 10 } 11 int hashCode() { 12 printf("This is 'template <typename T> class CalcHashClass'.\n"); 13 return _value.hashCode() + 10000; 14 } 15 private: 16 T _value; 17 }; 18 19 //2. int類型實例化特化模板類。 20 template <> 21 class CalcHashClass<int> { 22 public: 23 CalcHashClass(int const& v) : _value(v) { 24 } 25 int hashCode() { 26 printf("This is 'template <> class CalcHashClass<int>'.\n"); 27 return _value * 101; 28 } 29 private: 30 int _value; 31 }; 32 33 //3. const char*類型實例化的特化模板類 34 template<> 35 class CalcHashClass<const char*> { 36 public: 37 CalcHashClass(const char* v) { 38 _v = new char[strlen(v) + 1]; 39 strcpy(_v,v); 40 } 41 ~CalcHashClass() { 42 delete [] _v; 43 } 44 int hashCode() { 45 printf("This is 'template <> class CalcHashClass<const char*>'.\n"); 46 int len = strlen(_v); 47 int code = 0; 48 for (int i = 0; i < len; ++i) 49 code += (int)_v[i]; 50 return code; 51 } 52 53 private: 54 char* _v; 55 }; 56 57 //4. 輔助函數(shù),用于幫助調用者通過函數(shù)的參數(shù)類型自動進行類型推演,以讓編譯器決定該 58 //實例化哪個模板類。這樣就可以使調用者不必在顯示指定模板類的類型了。這一點和多態(tài)有 59 //點兒類似。 60 template<typename T> 61 inline int CalcHashCode(T v) { 62 CalcHashClass<T> t(v); 63 return t.hashCode(); 64 } 65 66 //5. 給出一個范例類,該類必須包含hashCode方法,否則將造成編譯錯誤。 67 class TestClass { 68 public: 69 TestClass(const char* v) { 70 _v = new char[strlen(v) + 1]; 71 strcpy(_v,v); 72 } 73 ~TestClass() { 74 delete [] _v; 75 } 76 public: 77 int hashCode() { 78 int len = strlen(_v); 79 int code = 0; 80 for (int i = 0; i < len; ++i) 81 code += (int)_v[i]; 82 return code; 83 } 84 private: 85 char* _v; 86 }; 87 88 int main() { 89 TestClass tc("Hello"); 90 CalcHashClass<TestClass> t1(tc); 91 printf("The hashcode is %d.\n",t1.hashCode()); 92 //這里由于為模板類TestClass提供了基于int類型的模板特化類,因此編譯器會自動選擇 93 //更為特化的模板類作為t2的目標類。 94 CalcHashClass<int> t2(10); 95 printf("The hashcode is %d.\n",t2.hashCode()); 96 97 //在上面的示例中,我們通過顯示的給出類型信息以實例化不同的模板類,這是因為模板類 98 //的類型信息是無法像模板函數(shù)那樣可以通過函數(shù)參數(shù)進行推演的,為了彌補這一缺失,我們可以 99 //通過一個額外的模板函數(shù)來幫助我們完成這一功能。事實上,這一技巧在Thinking in Java中100 //也同樣給出了。101 printf("Ths hashcode is %d.\n",CalcHashCode(10));102 printf("Ths hashcode is %d.\n",CalcHashCode("Hello"));103 return 0;104 }105 //This is 'template <typename T> class CalcHashClass'.106 //The hashcode is 10500.107 //This is 'template <> class CalcHashClass<int>'.108 //The hashcode is 1010.109 //This is 'template <> class CalcHashClass<int>'.110 //Ths hashcode is 1010.111 //This is 'template <> class CalcHashClass<const char*>'.112 //Ths hashcode is 500.
通過上面的示例可以看出,模板特化是依賴于編譯器在編譯期動態(tài)決定該使用哪個特化類,或是標準模板類的。相比于多態(tài)的后期動態(tài)綁定,該方式的運行效率更高,同時靈活性也沒有被更多的犧牲。
下面將給出一個結合模板特化和多態(tài)的示例(請注意代碼中的說明性注釋):
1 #include <stdio.h> 2 #include <string.h> 3 4 //1. 定義一個接口 5 class BaseInterface { 6 public: 7 virtual ~BaseInterface() {} 8 virtual void doPrint() = 0; 9 };10 11 //2. 標準模板類繼承該接口,同時給出自己的doPrint()實現(xiàn)。12 template<typename T>13 class DeriveClass : public BaseInterface {14 public: 15 void doPrint() {16 printf("This is 'template<typename T> class DeriveClass'.\n");17 }18 };19 20 //3. 基于int類型特化后的DeriveClass模板類,同樣繼承了該接口,也給出了自己的DoPrint()實現(xiàn)。21 template<>22 class DeriveClass<int> : public BaseInterface {23 public: 24 void doPrint() {25 printf("This is 'template<> class DeriveClass<int>'.\n");26 }27 };28 29 //4. 對象創(chuàng)建輔助函數(shù),該函數(shù)可以通過參數(shù)類型的不同,實例化不同的接口子類。30 template<typename T>31 inline BaseInterface* DoTest(T t) {32 return new DeriveClass<T>;33 }34 35 int main() {36 BaseInterface* b1 = DoTest(4.5f);37 b1->doPrint();38 BaseInterface* b2 = DoTest(5);39 b2->doPrint();40 delete b1;41 delete b2;42 return 0;43 }44 //This is 'template<typename T> class DeriveClass'.45 //This is 'template<> class DeriveClass<int>'.
二、模板部分特化:
有的書中將其翻譯成模板偏特化,或者是模板的局部特化,但含義都是相同的。為了便于理解,我們可以將上面的模板特化稱為模板全部特化,即模板類的類型參數(shù)全部被特化了。顧名思義,模板部分特化只是將其中一部分類型參數(shù)進行了特化聲明,因此也可以將模板特化視為模板部分特化的一種特殊形式。由于應用場景基本相同,因此下面的代碼將僅僅給出最基本的示例和注釋說明,以幫助大家熟悉他的語法即可:
1 //1. 標準模板類。 2 template<typename T1, typename T2> 3 class MyClass { 4 ... ... 5 }; 6 //2. 兩個模板參數(shù)具有相同類型的部分特化類。 7 template<typename T> 8 class MyClass<T,T> { 9 ... ...10 }11 //3. 第二個類型參數(shù)是int12 template<typename T>13 class MyClass<T,int> {14 ... ...15 }16 //4. 兩個模板參數(shù)都是指針。17 template<typename T1,typename T2>18 class MyClass<T1*,T2*> {19 ... ...20 }21 //5. 兩個模板參數(shù)都是相同類型的指針。22 template<typename T>23 class MyClass<T*,T*> {24 ... ...25 }26 //6. 調用示例代碼。27 int main() {28 MyClass<int,float> c1; //調用MyClass<T1,T2>29 MyClass<float,float> c2; //調用MyClass<T,T>30 MyClass<float,int> c3; //調用MyClass<T,int>31 MyClass<int*,float*> c4; //調用MyClass<T1*,T2*> 32 MyClass<int*,int*> c5; //調用MyClass<T*,T*>33 return 0;34 }
三、缺省模板實參:
和函數(shù)的缺省參數(shù)一樣,C++的模板也同樣支持缺省類型參數(shù)。
1 //1. 第二個類型參數(shù)的缺省值是vector<T> 2 template<typename T, typename T2 = std::vector<T> > 3 class MyClass { 4 ... ... 5 } 6 int main() { 7 MyClass<int> c1; //第二個類型參數(shù)是vector<int> 8 MyClass<int,list<int> > c2; //第二個類型參數(shù)是list<int> 9 return 0;10 }
這種使用缺省模板參數(shù)的代碼,在STL中比比皆是。
四、非類型模板參數(shù):
模板的類型參數(shù)不僅僅可以是類型,也可以是常量,但是常量本身的類型是有限制的,不是所有類型的常量都可以,目前只是整型常量和外部鏈接對象的指針可以,而浮點型等其他原始類型,或自定義類型均不可。
1 template<typename T, int MAXSIZE> 2 class MyContainer { 3 public: 4 int capacity() const { return MAXSIZE; } 5 ... ... 6 private: 7 T elements[MAXSIZE]; 8 }; 9 10 int main() {11 MyContainer<int,50> c1;12 return 0;13 }14 和普通類型模板一樣,非類型模板參數(shù)也可以有缺省值,如:15 template<typename T, int MAXSIZE = 10>16 class MyContainer {17 public:18 int capacity() const { return MAXSIZE; }19 ... ...20 private:21 T elements[MAXSIZE];22 };
最后需要說明的是,不管是普通模板類還是非類型模板類,只要其類型不同,或是常量值不同,就不能將其視為相同類型的對象,這一點同樣適用于模板函數(shù)。