类模板
-
同函数模板,大部分性质相同,类模板也用于代码的重用性体现,性质可见“函数模板”
-
定义格式:
template <class NAME1,class NAME2,...> class MyClass{ //... };
或
template <typename NAME1,typename NAME2,...> class MyClass{ //... };
(在模板中class 与 typename关键字的作用完全相同)
-
与一般类声明不同,在头文件中声明类模板时一般不将方法实现分离到单独的cpp文件,即整个类模板的所有代码体均写在头文件中。
模板类
-
即由类模板实例化生成的类,同函数模板一样,编译器不会为类模板本身分配空间,只有通过显式或隐式实例化类模板为模板类后,才会为每个单独的模板类分配空间。
-
显式实例化
template class class_name<int, double,...>; //注意此处的class关键字必须带上
-
隐式实例化,即直接使用类模板声明对象,编译器会根据给出的模板参数首先生成一个模板类,再使用该模板类创建一个对象
class_name<char> obj1; // <==> class class_name<char> obj1;
注:不同于模板函数,已经显式实例化的模板类在声明对象时,依旧需要给出<>和其中的模板参数 [ 0 ] ^{[0]} [0],让编译器确定生成何种类型模板类的对象,于类模板而言,显式实例化与隐式实例化的区别在于,显式实例化声明告诉编译器生成一个特定的模板类,无论是否用到该类声明对象,而隐式实例化只在声明对象的同时生成对应的模板类
注[0]:在c++17中支持模板参数的自动推导,即在保证由构造函数传入的参数能够推导出模板参数的前提下,可以直接使用类名实例化对象
-
模板的定义或声明只能在全局、命名空间、类范围内进行,不能在局部、函数内进行.
-
默认模板参数:对于类模板而言,可以为类模板指定一个默认的类型参数,当在使用模板声明对象时,就可以不用给出<>中的参数(<>本身需要给出),这时编译器采用默认的模板参数.
//e.g. template<class A = int> //指定默认的模板参数 class Defaul{}; Defaul<double> obj1; //先实例化Defaul<double>,再实例化对象obj1 Defaul<> obj2; //先实例化Defaul<int>,再实例化对象obj2 //<==> Defaul<int> obj2;
-
继承:类模板可以继承,存在下列几种情况:
- 一般类可以继承已经实例化的类模板(即模板类),即一般类之间的继承关系
class Common : public Temp<int, double,...>{ //... }; //Common为一般类
- 类模板可以继承一般类
template <class A> class Temp : public Common{ //... }; //Temp为模板
- 类模板可以继承类模板
template<class B, class C> class Othertemp : public Temp<B>{ //... }; //Othertemp为模板
- 一般类不能直接继承类模板本身
class Common : public Temp{ //... }; // ×
-
非类型参数:类模板的参数并不一定都是类型参数,可以像函数一样存在具体的值参数,即非类型参数,可以参考std::array<int, 5>的第二个参数就是非类型参数,非类型参数主要用于模板的所有实例公有的特定的值的传递
template <typename M, int size_> class Mylist{ std::array<M, size_>; //... };
当然,非类型参数也可以指定默认值,类似函数的形参默认值
-
类模板的特化:同函数模板一样,可以对某些特定的参数类型实例给出与通用模板类不一致的特定的类实现,其包括不同的数据成员或是成员函数等,定义方式如函数模板一致,参考下例:
template<typename A> class Calculator{ private: A member1; A member2; public: Calculator(A x, A y); A add(); A sub(); A mul(); A div(); };
对于上述运算器类,当传入模板参数为string时,显然无法执行
mul()
和div()
两个函数,即无法对string类对象相互做乘法和除法,如果错误的使用了该模板实例的对象,就容易报错,故此考虑对类模板特化:template<> class Calculator<string>{ //不同于函数模板,<>及其中的特化类型必须在类名后给出 private: A member1; A member2; public: Calculator(string x, string y); A add(); A sub(); };
特化中去除了乘法和除法两个成员函数,故此,当再传入string作为模板参数时,编译器发现该类型已经被特化了,也就不会去实例化类模板,而是直接使用该特化的类声明对象
注:类模板的特化本身实际上就是一个特殊的具体的类,编译器会在定义时就为相应的成员分配空间(如静态成员)
-
类模板的偏特化:当类模板具有多个模板参数时,如果只想针对其中部分参数实现模板的特化,则称之为类模板的偏特化,如下例:
template<typename A,int SIZE> class Calculator{ private: std::array<A, SIZE> member; public: Calculator(...); A all_add(); A all_mul(); };
对于上例,考虑当传入模板参数为string时,成员函数
all_mul()
无法使用,但如果使用类模板的特化,又无法确定实际使用中第二个模板参数SIZE的具体大小,故此考虑类模板的偏特化template<int SIZE> class Calculator<string, SIZE>{ private: std::array<string, SIZE> member; public: Calculator(...); A add(); };
偏特化的模板形参中保留不进行偏特化的形参,其余格式同特化类似,在该类模板针对第一个参数为string时的偏特化的特定实现中去掉了
all_mul()
成员函数,也就避免了相应错误的发生
注:值得区分,特化是对类模板某一特定类型给出特定的实现,其特化后的本质就是一个特殊的类,而偏特化则是针对某一具有相同性质的所有类型的集合给出特定的实现,其偏特化后的本质还是一个模板(一个所能处理的数据类型更具体的模板),类似函数模板的重载,偏特化后所能处理的类型的集合包含于原有的类模板所能处理的类型的集合