Step By Step(C++模板类)

来源:互联网 发布:淘粉吧是淘宝客吗 编辑:程序博客网 时间:2024/05/16 05:11

 和函数一样,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 = 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     };
复制代码

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

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

#8楼   

2012-09-25 11:37 by luohao  
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;
};
支持(0)反对(0)

#9楼[楼主]   

2012-09-25 11:53 by Stephen_Liu  
@luohao
呵呵,谢谢补充。
支持(0)反对(0)

#10楼   

2012-10-29 09:59 by likebeta  
楼主这里CalcHashClass(T const& v) : _value(v)好像是浅拷贝, 内存释放有问题
支持(0)反对(0)

#11楼[楼主]   

2012-10-29 10:28 by Stephen_Liu  
@likebeta
应该是没有问题的。对于char*等类型做了部分特化,实现中也重新分配了内存。对于你这里提到的模板类型,如果T是class或struct,完成深拷贝的工作是需要在T所代表的类型中完成的。
支持(0)反对(0)

#12楼   

2012-10-29 11:58 by likebeta  
@Stephen_Liu
但是你这个TestClass的拷贝构造函数没有重写, 是浅拷贝吧, 你运行没有出错吗? vs2008析构出错
支持(0)反对(0)

#13楼[楼主]   

2012-10-29 12:46 by Stephen_Liu  
@likebeta
谢谢,是有问题,8楼的已经指出了,我在9楼也承认过了。


0 0