Step By Step(C++模板类)
[来源] 达内 [编辑] 达内 [时间]2012-09-12
和函数一样,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 };
最后需要说明的是,不管是普通模板类还是非类型模板类,只要其类型不同,或是常量值不同,就不能将其视为相同类型的对象,这一点同样适用于模板函数。