Step By Step(C++模板類)

http://www.cnblogs.com/stephen-liu74/archive/2012/08/22/2599400.html

    和函數一樣,C++中的class也可以類型參數化,其中容器類是極具這一特徵的。對於模板類的基本定義和使用,可以參考STL,這裏就不做過多的贅述了。下面將主要介紹一下與其相關的高級實用特徵。

一、模板的特化:

    這裏可以先將類模板特化與面向對象中的多態進行一個簡單的比較,這樣可以便於我們對它的理解,也同樣有助於指導我們在實際的開發中應用這一C++技巧。衆所周知,對於多態而言,提供的是統一的接口和不同的實現類實例,其最終的行爲將取決於實現類中的實現,相信每一個有面向對象基礎的開發者對於這一概念並不陌生。而模板特化則要求必須提供一個標準的模板類(等同於多態中的接口),與此同時再爲不同的類型提供不同的特化版本。在模板特化類中,我們首先需要保證類名是相同的,只是模板參數不再使用抽象的類型,而是直接使用具體的類型來實例化該模板類。在調用時,編譯器會根據調用時的類型參數自動選擇最爲合適的模板類,即如果有特化模板類中的類型參數和當前調用類型相匹配的話,則首選該特化模板類,否則選擇最初的標準模板類。見如下用例(請注意代碼中的說明性註釋)

複製代碼
  1     #include <stdio.h>
  2     #include <string.h>
  3     
  4     //1. 這裏我們先聲明瞭一個通用類型的模板類。這裏要有類型參數必須包含hashCode()方法。
  5     //否則,該類型在編譯期實例化時將會導致編譯失敗。
  6     template <typename T>
  7     class CalcHashClass { //該類爲標準模板類(等同於多態中的接口)
  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. 輔助函數,用於幫助調用者通過函數的參數類型自動進行類型推演,以讓編譯器決定該
 58     //實例化哪個模板類。這樣就可以使調用者不必在顯示指定模板類的類型了。這一點和多態有
 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         //的類型信息是無法像模板函數那樣可以通過函數參數進行推演的,爲了彌補這一缺失,我們可以
 99         //通過一個額外的模板函數來幫助我們完成這一功能。事實上,這一技巧在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.
複製代碼

    通過上面的示例可以看出,模板特化是依賴於編譯器在編譯期動態決定該使用哪個特化類,或是標準模板類的。相比於多態的後期動態綁定,該方式的運行效率更高,同時靈活性也沒有被更多的犧牲。
    下面將給出一個結合模板特化和多態的示例(請注意代碼中的說明性註釋)

複製代碼
 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()實現。
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()實現。
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. 對象創建輔助函數,該函數可以通過參數類型的不同,實例化不同的接口子類。
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>'.    
複製代碼

    
二、模板部分特化:

    有的書中將其翻譯成模板偏特化,或者是模板的局部特化,但含義都是相同的。爲了便於理解,我們可以將上面的模板特化稱爲模板全部特化,即模板類的類型參數全部被特化了。顧名思義,模板部分特化只是將其中一部分類型參數進行了特化聲明,因此也可以將模板特化視爲模板部分特化的一種特殊形式。由於應用場景基本相同,因此下面的代碼將僅僅給出最基本的示例和註釋說明,以幫助大家熟悉他的語法即可:

複製代碼
 1     //1. 標準模板類。
 2     template<typename T1, typename T2>
 3     class MyClass {
 4         ... ...
 5     };
 6     //2. 兩個模板參數具有相同類型的部分特化類。
 7     template<typename T>
 8     class MyClass<T,T> {
 9         ... ...
10     }
11     //3. 第二個類型參數是int
12     template<typename T>
13     class MyClass<T,int> {
14         ... ...
15     }
16     //4. 兩個模板參數都是指針。
17     template<typename T1,typename T2>
18     class MyClass<T1*,T2*> {
19         ... ...
20     }
21     //5. 兩個模板參數都是相同類型的指針。
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     }
複製代碼

    
三、缺省模板實參:

    和函數的缺省參數一樣,C++的模板也同樣支持缺省類型參數。

複製代碼
 1     //1. 第二個類型參數的缺省值是vector<T>
 2     template<typename T, typename T2 = std::vector<T> >
 3     class MyClass {
 4         ... ... 
 5     }
 6     int main() {
 7         MyClass<int> c1;            //第二個類型參數是vector<int> 
 8         MyClass<int,list<int> > c2; //第二個類型參數是list<int> 
 9         return 0;
10     }
複製代碼

    這種使用缺省模板參數的代碼,在STL中比比皆是。
    
四、非類型模板參數:

    模板的類型參數不僅僅可以是類型,也可以是常量,但是常量本身的類型是有限制的,不是所有類型的常量都可以,目前只是整型常量和外部鏈接對象的指針可以,而浮點型等其他原始類型,或自定義類型均不可。

複製代碼
 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     和普通類型模板一樣,非類型模板參數也可以有缺省值,如:
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     };
複製代碼

    
    最後需要說明的是,不管是普通模板類還是非類型模板類,只要其類型不同,或是常量值不同,就不能將其視爲相同類型的對象,這一點同樣適用於模板函數。


TestClass的定義有問題
class TestClass {
public:
TestClass(const char* v) {
int len = strlen(v);
_v = new char[len+1];
memset(_v,0,len+1);
strcpy(_v,v);
}

TestClass(const TestClass &t) { //開始沒加這個很危險,有指針變量的都要加上覆制函數
int len = strlen(t.getV());
_v = new char[len+1];
memset(_v,0,len+1);
strcpy(_v,t.getV());
}

~TestClass() {
delete _v; //被釋放了2次
}

//int len_of_v() {return strlen(v);}
const char *getV() const {return _v;}
public:
int hashCode() {
int len = strlen(_v);
int code = 0;
for (int i = 0; i < len; ++i)
code += (int)_v[i];
return code;
}
private:
char* _v;
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章