Step By Step(C++模板类)

[来源] 达内    [编辑] 达内   [时间]2012-09-12

和函数一样,C++中的class也可以类型参数化,其中容器类是极具这一特征的。对于模板类的基本定义和使用,可以参考STL,这里就不做过多的赘述了。下面将主要介绍一下与其相关的高级实用特征。

和函数一样,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. 第二个类型参数是int12     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 = stvector<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     };

    
    最后需要说明的是,不管是普通模板类还是非类型模板类,只要其类型不同,或是常量值不同,就不能将其视为相同类型的对象,这一点同样适用于模板函数。

资源下载