目录
[二. 构建模板特化](#二. 构建模板特化)
[(2. 类模板特化](#(2. 类模板特化)
[1.全特化 (相当于在一群中,服务一个人)](#1.全特化 (相当于在一群中,服务一个人))
[2. 解决方案](#2. 解决方案)
前言
本文是模板初阶文章,建议先学习下文,更有利于理解
【C++】是内存管理,但C++ !! && 模板初阶_花果山~~程序猿的博客-CSDN博客
一,非类型模板参数
1,前言
我们回顾我们之前所使用的模板场景,大部分场景是作为类型模板。
cpp
template < class T>
7 class arry{
8 private:
9 public:
10 T net;
11 };
但我们是否能回想起曾经的场景:
cpp
21 typedef N1 10;
E> 22 typedef N2 10;
E> 23 typedef N3 10;
24
E> 25 int main()
26 {
E> 27 int n1[N1];
E> 28 int n2[N2];
E> 29 int n3[N3];
30 return 0
}
这个例子不是很贴切。需要定义多个宏,才能达到这样的效果,比较繁琐,而非模板参数可以解决这个问题
cpp
6 template < class T, size_t N = 20>
7 class arry{
8 private:
9 public:
10 T net[N];
11 };
12
13 int main()
14 {
W> 15 arry<int, 10> n1;
W> 16 arry<int, 10> n2;
17 return 0;
18 }
可见,非模板参数是可以设置缺省参数,但值得注意的是参数类型一定得是整型(布尔类型也可以,float,double不兼容),且N不允许修改。
总之:
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
补充:STL中的array接口代替原有数组,确实有全面检查越界好处,但不如直接用vector来的实在。
2.场景
通常情况下, 使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结 果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板。
cpp
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
int main()
{
cout << Less(1, 2) << endl; // 可以比较,结果正确
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl; // 可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 可以比较,结果错误
return 0;
}
可见p1, p2是指针,而这种类型无法使用此模板,因此我们需要进行模板特化。
二. 构建模板特化
即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式 。模板特化中分为函数模板特化 与类模板特化。
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表必须要和模板函数的 基础参数类型 完全相同,如果不同编译器可能会报一些奇怪的错误
(1.函数模板特化
cpp
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
// 全特化
// 对Less函数模板参数类型进行指定一个唯一类型
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
// 偏特化
template<>
bool Less<T*>
int main()
{
cout << Less(1, 2) << endl;
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl;
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了
return 0;
}
上面的例子,就是为参数是指针对象进行比较。普通的模板无法比较,只能再写一个重载函数。
(2. 类模板特化
1.全特化 (相当于在一群中,服务一个人)
全特化即是将模板参数列表中所有的参数都确定化。
cpp
// 类模板
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
// 类模板全特化
template<>
class Data<int, char>
{
public:
Data() {cout<<"Data<int, char>" <<endl;}
private:
int _d1;
char _d2;
};
2.偏特化(相当于服务一类人)
(1)部分特化
将模板参数类表中的一部分参数特化。
cpp
// 类模板全特化
template<>
class Data<int, char>
{
public:
Data() {cout<<"Data<int, char>" <<endl;}
private:
int _d1;
char _d2;
};
// 类模板偏特化
template<>
class Data<T, char>
{
public:
Data() {cout<<"Data<int, char>" <<endl;}
private:
T _d1;
char _d2;
};
(2)参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数 更进一步的条件限制所设计出来的一个特化版本。
比如:指针类型,引用类型比较特别
cpp
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
Data(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout<<"Data<T1&, T2&>" <<endl;
}
private:
const T1 & _d1;
const T2 & _d2;
};
void test2 ()
{
Data<double , int> d1; // 调用特化的int版本
Data<int , double> d2; // 调用基础的模板
Data<int *, int*> d3; // 调用特化的指针版本
Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}
注意:当我们拷贝到编译器中时,发现我们运行不了这段代码,原因是什么呢?
原因:因为类模板的全特化,偏特化不是全新的模板,是需要存在基于原来的类模板为基础的代码,才能语法通过。
从结果来看,就像厨师做菜一样,为了保证出菜效率,我们会选择成品,半成品,原材料。全特化就是成品,偏特化则是半成品,原材料则是基础模
板。
三,模板分离编译
1.现象
测试一下场景,函数模板声明,定义分离在两个文件中的场景。
cpp
// a.h
template<class T>
T Add(const T& left, const T& right);
func();
// a.cpp
#inlclue "a.h"
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
void func()
{
cout << "func" << endl;
}
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
func();
return 0;
}
结果:你会发现,编译不通过。而我们注释掉main函数中的Add调用后,就能编译通过。
分析:
我们知道代码编译过程有,预处理(头文件展开,条件编译,剔除注释) ------> 编译(进行语法检查,生成语法树,生成汇编代码(给人看的)) ------> 汇编(生成机器码,注意:头文件不参与编译)------> 链接(根据修饰后的字符名,补充跳转地址)
其实是问题出在链接,文件之间在前3个过程,相互独立编译。编译时,普通函数可以生成地址,而函数模板因没有实例化所以无法生成地址,在链接时期,无法解决地址问题,所以无法编译成功。
啥?你问为啥函数模板不像普通函数一样,去其他文件中寻找定义并且实例化呢?
答:
先回答普通函数,普通函数的地址就是其函数定义代码的第一行地址,在链接时在函数声明处被填充,CPU可以快速找到。
而函数模板,因为链接时没有留下地址(因为T未实例化),得靠修饰后的函数名在大量的文件中寻找,本身这是一个很浪费资源的操作,所以编译器索性懒得寻找模板函数定义的位置,只允许函数模板定义与声明在同一个文件中。(方便查找到模板函数,并且实例化)
2. 解决方案
1.显示实例化(不推荐------治标不治本)
在模板函数定义位置,进行显示实例化。
cpp
// a.cpp
#inlclue "a.h"
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
void func(){cout << "func" << endl;}
template //2参数是小数
double Add<double>(const double& left, const double& right);
template // 2参数是整型
int Add<int>(const int& left, const int& right);
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
func();
return 0;
}
缺陷是被动添加,每当出现一个新参数就需要添加,丧失模板的灵活性。
2.定义与声明头文件展开时在同一文件(最有效)
如小标题一样,定义与声明在头文件展开后要在同一文件中。
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。