模板进阶
非类型模板参数
模板参数分为类型形参和非类型形参
类型形参是出现在模板参数中,跟在class或者typename之后的参数类型名称
非类型形参是使用常量作为模板的一个参数,在类或者函数中可以作为常量使用
例如
1 | namespace xu |
注意:
- 浮点数、类对象、字符串不允许作为非类型模板参数
- 非类型的模板参数必须在编译期就是一个确定的结果(数值)
模板的特化
概念
模板特化可以解决一些,对于特殊类型的特殊模板化处理的问题,如果不采取这样的方式,就有可能会导致错误的结果
例如
1 | template<class T> |
对于第三种情况,我们就需要对这种类型(指针)进行特化,由此就分为函数模板特化和类模板特化
函数模板特化
模板特化之前,需要有一个基础的函数模板,在template之后加一个空的尖括号,在函数名之后加一对尖括号,其中放入需要特化的类型
需要注意的是,特化的函数形参表必须和模板函数的基础参数类型完全相同
1 | template<class T> // 模板函数 |
注意:通常在遇到不能处理的模板参数时,都是直接给出更加匹配的一般函数,而不采用函数模板特化的方法
类模板特化
全特化
全特化指所有的模板参数都确定
例如
1 | template<class T1, class T2> // 模板类 |
偏特化
偏特化就是针对模板参数进行条件限制的特化版本,分为部分特化和参数更进一步的限制,例如:
1 | template<class T1, class T2> |
部分特化
1
2
3
4
5
6
7
8
9
10
11
12template<class T1>
class Data<T1, int>
{
public:
Data()
{
cout << "Data<T1, int>" << endl;
}
private:
T1 _d1;
int _d2;
};参数限制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33template<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()
{
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 | // tp.h |
这种报错是属于链接时报错,C++程序运行需要经历四个阶段,分别是预处理、编译、汇编、链接
因为头文件不参与编译,不会对Add函数模板实例化,不会生成具体的加法函数,也就不存在所谓的函数地址,因此在obj文件链接时,main函数调用Add时寻找地址时找不到结果,因此链接时报错
解决方法
- 将声明和定义放到同一个文件中,例如hpp或者h文件,推荐这种方式
- 在模板定义的位置显示实例化,不推荐使用
模板优缺点
优点:提高了代码的复用性,节省资源,可以更快的迭代开发,例如STL,增强了代码的灵活性
缺点:会导致代码膨胀问题,导致编译时间边长;出现模板编译错误时,错误信息非常凌乱,难以定位