目录
非类型模板参数
引入
分类
使用typename的特殊情况
注意点
模板特化
引入
介绍
函数模板特化
使用
编辑
优点
类模板特化
全特化
偏特化
部分特化
特殊的特化
使用
分离编译
介绍
问题代码示例
代码
说明
预处理
编译
链接
类模板实例化原理
总结
解决方法
显式实例化
声明和定义放在一个头文件
非类型模板参数
引入
一般我们使用模板都是想让这个类兼容更多的类型,但模板参数不止只有这个作用
分类
模板参数分为 类型形参 与 非类型形参
- 类型形参 : 出现在模板参数列表中,跟在class或者typename之后的参数类型名称(这两个一般情况下可以随意使用,但在某些情况下只能使用typename)
- 非类型形参 : 就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用(eg: 定义数组大小)
使用typename的特殊情况
- 当我们需要使用一个类里的类型时, 就得使用::来指明该类型所属的类名
- 但在编译期间,编译器是不知道这个东西到底是一个类型,还是一个静态成员变量(根据语法来说,静态成员变量也可以被这样调用)
- 如果这里是变量,我们用变量作为类型来创建变量,那语法就错误了
- 而这里仅仅只是类名,并没有实例化,编译器无法通过代码判断是否合规(没有实例化就找不到对应的代码)
- 所以为了安抚(?)编译器,需要在类名的前面 + typename,来告诉编译器,这里是一个类型,不要觉得有语法错误 ,具体等实例化后去类里找就行
注意点
- " 浮点数、类对象以及字符串 " 是不允许作为非类型模板参数的,只能是整型(最初创造出来就是为了处理大小啥的)
- 非类型的模板参数必须在编译期就能确认结果,C++的模板机制要求 -- 编译器在编译期间就生成模板的实例化代码,而这些代码的生成需要基于已知的、在编译期就可以确定的参数值
模板特化
引入
之前在优先级队列中,我们有使用仿函数实现比较函数
当时如果传入参数是自定义类型时,需要我们另写一种比较函数,但也可以采用另外一种方法 -- 函数模板特化
介绍
特化是在原模板类的基础上,针对特殊类型 所进行特殊化的实现方式模板特化分为 -- 函数模板特化 与 类模板特化
函数模板特化
使用
在需要特化的函数名后 + <特化的类型>,这样这里就不需要T类型,但仍需要写tmplate<>
优点
按照原先的方法,直接给出对应函数,那么可能在传参的时候就费劲点,可能需要把所有参数都给出
但如果有函数特化,就不需要传入后两个参数,直接传类型就够了(因为此时函数名仍是当时给出的缺省参数)
类模板特化
写法是一样的,只不过针对的对象不一样,一个是函数,一个是类
全特化
将类的所有模板参数都进行特化(都给出具体类型)
偏特化
部分特化
将模板参数类表中的一部分参数特化
特殊的特化
可以将普通类型特化成该类型的指针/引用版本
使用
之前这样的类无法将自定义类型的指针传入进行比较(因为我们要的是比较对象),所以我们是另写了一个新类来完成该功能
但现在我们也可以使用特化的方式来实现
分离编译
介绍
分离编译是一种编程技术,允许将程序代码分割成多个文件,每个文件可以独立地编译成目标文件(.o文件),然后将这些目标文件链接在一起, 来创建最终的可执行程序
分离编译的核心思想 -- 是将代码逻辑分解为多个独立的模块,每个模块可以单独编译,从而减少了重新编译整个项目的需要
问题代码示例
一般来说,分离编译虽然很方便,但也很容易出现不少问题
代码
比如下面的三个文件
stack.h:
stack.cpp:
main.cpp:
说明
预处理
在这三个文件形成可执行文件的过程中,首先是在两个cpp文件中,将.h文件展开... ,形成.i文件
编译
- 这样stack.i中,有stack类的声明和定义,还有A类的func1定义 ; 但main.i中只有stack类的所有声明+部分定义 , 和A类的所有声明
- 所以在编译阶段,只能确定 main.i中的size(),以及stack类的构造函数 的地址(这里只是生成了与地址相关的信息,但这些信息还没有映射到最终的绝对地址 , 编译阶段的地址是为后续阶段提供信息的一种中间状态)
- 以及生成修饰后的函数名
- 虽然没有确定所有函数的地址,但是编译是可以通过的,因为含有对应函数的声明
接下来生成.o文件
上面这三个阶段,对应生成地址的情况如下图:
链接
- 接下来就是进行链接,链接是可以将func1的地址确定的,因为func1的定义有在stack.cpp中,俩文件相互一交流(拿着修饰后的函数去符号表找),就可以确定辽
- 但素,这样的代码会报错,编译器说push在链接中出现问题
- 原因是在main.s中,push修饰后的函数名适合int有关的(因为实例化传入的类型就是int),但是在stack.s中,没有人告诉他要实例化成什么,所以并没有实际生成代码,只是一个壳子,也就没有什么地址,自然在符号表中找不着
类模板实例化原理
- 编译器在遇到类模板的使用时,会生成一个用于在实际需要时实例化代码的计划,这个计划称为模板的实例化
- 这个计划会记录下特定模板参数,以及生成实际代码的需要
- 在源文件中使用模板时,需要提供模板参数,并创建一个模板的实例 , 这样就会告诉编译器 我们想要使用特定类型来实例化模板
- 当编译器在编译源文件时,会根据计划生成 特定类型的类代码,其中包含了特定类型的成员函数和数据成员
总结
所以,上面的情况之所以出现问题,就是因为类的具体代码不在当前编译单元中
编译器需要生成实际的代码,但如果实际的代码在其他源文件中,链接器就无法找到这些代码,从而导致链接错误
解决方法
显式实例化
如果一定要把类的声明和定义放在两个文件,可以采用显式实例化模板的方式
但是坏处就是,一旦使用新的类型实例化模板,就得加代码,hin麻烦
声明和定义放在一个头文件
这样在包括头文件的源文件中,一旦源文件实例化了类,就能及时的在编译过程生成实际代码,然后可以链接成功