C++模板进阶
文章目录
- 1. 模板参数
- 2. 模板的特化
- 2.1 为什么要有模板特化
- 2.2 怎样写模板特化
- 2.2.1 函数模板的特化
- 2.2.2 类模板的特化
- 2.2.2.1 全特化
- 2.2.2.2 偏特化
- 3. 模板分离编译
- 3.1 为什么会报链接错误
- 3.2 如何解决链接错误
- 4. 模板总结
1. 模板参数
模板参数分为两类:类型参数和非类型参数。
有关键字class或typename修饰的参数为类型参数;而非类型参数即用一个常量作为模板参数,在模板中可以作为常量使用。
具体示例如下:
上图中,T便是类型参数,而N便是非类型参数。
需要说明的是,浮点数、类对象和字符串是不允许作为非类型参数的,也就是说只有整型家族才能作为非类型参数。
还有一点就是,非类型参数必须在编译期间就能确定,否则会报错。
2. 模板的特化
2.1 为什么要有模板特化
C++中模板的引入,使得我们可以写出一些与类型无关的函数和类,对于大部分类型而言,模板的实例化都可以很好地实现目的;但是总有几个特殊类型,模板的直接实例化无法达到目的,因此对于这些特殊的类型,我们需要特殊处理。这就是模板特化的由来。
举一个例子,我们写了一个比较数大小的模板,但我们希望这个模板也能用于指针,而实际上我们不是比较地址的大小,而是比较指针所指向的对象的大小,在这种时候,模板的实例化就没办法满足要求。
期望的输出结果为0和0,实际输出结果为0和1。
2.2 怎样写模板特化
2.2.1 函数模板的特化
上图便是函数模板的特化,以下说明几个点:
- 函数模板的特化必须要有基础模板
- 在关键字template后加上一对尖括号
- 函数名后加上一对尖括号,尖括号内指明特化的具体类型
- 特化函数的形参必须与原函数模板的形参完全相同,但是将原函数模板中的模板参数替换成具体的类型,否则会报一些奇怪的错误。
此时,便可以正常对指针进行比较,结果均输出为0。
但是,在实际工程中,函数模板的特化使用得不多,一般对于需要特殊处理的类型,直接给出相应函数即可,无需函数模板的特化。因此,博主建议函数模板的特化少用。
直接给出即可,结果是一样的。
2.2.2 类模板的特化
类模板的特化有全特化和偏特化之分。
类模板特化,写法与函数模板特化基本相同,不过有一点区别,就是类模板的特化相对原来的类模板,没有要完全一一对应的硬性要求,不过最好还是对应地写。
2.2.2.1 全特化
类模板的全特化,即将模板特殊全部给出特定类型。如下所示:
2.2.2.2 偏特化
偏特化分为两种。
一种是模板参数列表中部分参数的特化,如下所示:
另外一种则是对模板参数进行进一步的限制,如下所示:
在上图中,类模板的偏特化分别对模板参数进行了指针和引用的进一步限制,这也是一种偏特化。
3. 模板分离编译
3.1 为什么会报链接错误
在将模板分离编译前,我们必须讲清楚什么是分离编译。
一个程序或项目,通常由多个源文件构成,而每个源文件单独编译生成一个目标文件,最后这些目标文件通过链接合并成一个可执行文件,这整个过程称作分离编译。
那么什么是模板的分离编译呢?将模板的声明放在头文件中,将模板的具体定义放在源文件中,这就是模板的分离编译。
模板的分离编译会报链接错误,这实际上因为模板的定义与涉及模板实例化的相关代码并不在同一个源文件中,而源文件是分离编译的,这就导致模板定义所在的源文件编译时,模板没有实例化,并不是一个具体的函数或类,这就导致在生成符号表时,不会有一个有效的地址。这样,在链接中的符号表的合并与重定位时,相应的本该由模板实例化出的函数或类,缺失有效地址,因而出错。 这也是为什么报链接错误的原因。
3.2 如何解决链接错误
最好的做法无疑就是不要使用模板分离编译,将模板的声明与定义都放在同一个文件中(.h 或 .hpp)
当然,如果一定要模板分离编译的话,还可以通过在模板定义的源文件中,显式实例化来解决。
以下举一个函数模板分离编译,并通过显式实例化解决的样例:
4. 模板总结
C++中模板的引入,实现了代码的复用,增强了代码的灵活性,大大减少了程序员的重复工作量,同时C++标准模板库 (stl) 也应运而生。
但是模板也有缺陷,本质上模板的实例化是将原本要由人重复的工作交给了编译器,所以实际上模板会使得代码膨胀,增加编译器负担,也因此增加编译时间。