Step By Step(C++模板类)(转)
Step By Step(C++模板类)
Posted on 2012-08-22 07:13 Stephen_Liu 阅读(6074) 评论(13) 编辑 收藏 和函数一样,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 };
最后需要说明的是,不管是普通模板类还是非类型模板类,只要其类型不同,或是常量值不同,就不能将其视为相同类型的对象,这一点同样适用于模板函数。
关于函数模板和类模板,模板参数并不局限于类型,普通值也可以作为模板参数。
当要使用基于值的模板时,必须显式地指定这些值,才能够对模板进行实例化,并获得最终代码。
详细解释可参考一下代码:
stack4.hpp
#ifndef _STACK4_H_
#define _STACK4_H_
#include <stdexcept>
/**
* @class:栈的类模板
* @param: typename T: 模板参数,栈存储元素的类型
* @param:int MAXSIZE: 栈元素的最大个数
* note:模板参数并不局限于类型,普通值也可以作为模板参数
*/
template<typename T, int MAXSIZE>
class Stack
{
private:
T elems[MAXSIZE]; //包含元素的数组
int numElems; //元素的当前个数
public:
Stack();
void push(T const&); //压入元素
void pop(); //弹出元素
T top() const; //返回栈顶元素
bool empty() const //返回栈是否为空
{
return numElems == 0;
}
bool full() const //返回栈是否已满
{
return numElems == MAXSIZE;
}
};
//构造函数/////////////////////////////////////////////
template<typename T, int MAXSIZE>
Stack<T, MAXSIZE>::Stack()
:numElems(0) //初始时栈不含元素
{
}
//压入元素/////////////////////////////////////////////
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push( T const& elem)
{
if(numElems == MAXSIZE)
{
throw std::out_of_range("Stack<>::push(): stack is full");
}
elems[numElems] = elem; //压入元素
++numElems; //增加元素个数
}
//弹出元素/////////////////////////////////////////////
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
if(numElems <= 0)
throw std::out_of_range("Stack<>::pop(): empty stack");
--numElems; //减少元素个数
}
//返回栈顶元素/////////////////////////////////////////
template<typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::top() const
{
if(numElems <= 0)
throw std::out_of_range("Stack<>::pop(): empty stack");
return elems[numElems - 1]; //返回最后一个元素
}
#endif
main.cpp
/************************************************************************
* @filename: non-type template parameters
* @author: JackyLiu
* @Date: 2013-6-26
*************************************************************************/
#include <iostream>
#include <string>
#include <cstdlib>
#include "stack4.hpp"
int main(int argc, char* argv[])
{
try
{
Stack<int, 20> int20Stack; //可以存储20个int元素的栈
Stack<int, 40> int40Stack; //可以存储40个int元素的栈
Stack<std::string, 40> stringStack; //可以存储40个string元素的栈
//使用可存储20个int元素的栈
int20Stack.push(7);
std::cout << int20Stack.top() << std::endl;
int20Stack.pop();
//使用可存储40个string元素的栈
stringStack.push("hello");
std::cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch(std::exception const& ex)
{
std::cerr << "Exception: " << ex.what() << std::endl;
int i;
std::cin >> i;
return EXIT_FAILURE; //退出程序且有ERROR标记
}
}
运行结果:


浙公网安备 33010602011771号