C++ 模板
一、引言
C++ 通过函数重载可以实现对不同数据类型完成同一功能,但是这样写重复的代码就显得很臃肿,且代码的复用率较低,每当出现一个 新类型,就需要增加对应的函数。
由此,引入了模板。所谓模板,顾名思义就是一个通用的描述。也就是使用泛型来定义函数,就是编写与类型无关的代码,其中泛型可通过具体的类型来(如 int 或 double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的的函数。
二、函数模板
2.1 函数模板格式
函数模板的基本格式如下:
1 | |
例子:
1 | |
模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换 int 的函数时,编译器将按模板模式创建这样的函数,并用 int 代替 anyType。
这就是相当于创建了两个函数 void Swap(int &a, int &b) 和 void Swap(double &a, double &b)。
2.2 实例化
隐式实例化、显式实例化和显式具体化统称为具体化(specialization)。它们的相同之处在于,它们表示的都是使用具体类型的函数定义,而不是通用描述。
2.2.1 隐式实例化
隐式实例化就是让编译器根据实参推演模板参数的实际类型。就像上面的例子那样,只管调用 Swap(i, j) 即可,具体的 i 和 j 的类型由编译器自行推导。
但若是这样调用:Swap(i, a) 编译就会报错,因为编译器无法辨认这里的 ansType 到底是 int 类型还是 double 类型。一个方法就是我们自己来强制转换,另一个方法就是下面要提到的显示实例化。
2.2.2 显式实例化
显示实例化就是在函数名后的 <>中指定模板参数的实际类型,例如:
1 | |
1 | |
这里会将 x 强制转换为 int 类型。那如果对 Swap 做类似的处理呢?
这里如果调用 Swap<int>(i, a) 则会报错。注意,这里的形参类型为 double &,所以不能指向 int 类型的变量 i。
2.2.3 显示具体化(特化)
模板是一种泛型技术,它能接受的类型是宽泛的、没有限制的,并且对这些类型使用的算法都是一样的(函数体或类体一样)。但是现在我们希望改变这种“游戏规则”,让模板能够针对某种具体的类型使用不同的算法(函数体或类体不同),这在 C++ 中是可以做到的,这种技术称为模板的显示具体化(Explicit Specialization)。
下面看一个例子就明白了:
1 | |
显示具体化有下面两种写法,它们是等价的:
1 | |
它和显式实例化的区别在于,这些声明的意思是“不要使用 Swap() 模板来生成函数定义,而应使用专门为 Job 类型显式得定义的函数定义“。这些原型必须有自己的函数定义。显式具体化声明在关键字 template 后包含 <>,而显式实例化没有。
在同一个文件(或转换单元)中使用同一种类型的显式实例和显式具体化将出错
2.3 模板参数的匹配规则
对于函数重载、函数模板和函数模板重载,C++需要(且有)一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析(overloading resolution)。大致过程如下。
- 创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数
- 使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。例如,使用 float 参数的函数调用可以将该参数转换为 double,从而与 double 形参匹配,而模板可以为 float 生成一个实例
- 确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用出错
参数匹配过程中,从最佳到最差的顺序如下所述:
- 完全匹配,但常规函数优先于模板
- 提升转换(例如,char 和 short 自动转换为int,float 自动转换为 double)
- 标准转换(例如,int 转换为 char,long 转换为 double)
- 用户定义的转换,如类声明中定义的转换
其中完全匹配允许的无关紧要转换:
| 从实参 | 到形参 |
|---|---|
| Type | Type & |
| Type & | Type |
| Type [] | * Type |
| Type (argument-list) | Type (*) (argument-list) |
| Type | const Type |
| Type | volatile Type |
| Type * | cosnt Type |
| Type * | volatile Type * |