一、模板函数的实例化与具体化
template <typename T>
void Swap(T &a, T &b)
{
T tmp = a;
a = b;
b = tmp;
}
1.实例化
首先,对于上面一段代码,这是一个函数模板,是对”交换“两个通用数据类型的函数的描述。注意,此时并没有具体的函数定义(或者函数实现)。
程序在编译阶段,编译器会根据Swap函数的实参的类型,自动生成该类型对应的Swap函数的定义(或函数实现),这个过程,成为函数模板的实例化(Instantiation)
2.具体化
现在考虑另外一种情况,假设有一个自定义的类
class studentInfo
{
private:
int idNum;
int age;
string name;
char gender;
float score;
}
要”交换“两个学生的分数score,那么用Swap函数明显是不合适的,最上面的模板函数也不支持面向studentInfo类的”交换“功能。这种情况下,可以定义一个具体化的(特殊的,特例的)函数定义,我们希望编译器调用Swap函数交换两个studentInfo对象的score值时,实际调用的是这个函数,而不再去寻找模板。这个过程叫做函数模板的具体化(specialization). 注意声明时template <>中没有任何东西,即不需要通用数据类型!
// 交换两个学生的分数, 注意,具体化函数的函数定义中不再有通用数据类型T了
template<> void Swap(studentInfo& s1, studentInfo& s2)
{
float tmpScore = s1.score;
s1.score = s2.score;
s2.score = tmpScore;
}
//或者下面这种写法也支持,唯一的区别是函数声明中加了指定的数据类型<studentInfo>
//template<> void Swap<studentInfo>(studentInfo& s1, studentInfo& s2)
//{
// float tmpScore = s1.score;
// s1.score = s2.score;
// s2.score = tmpScore;
//}
3.一些规则
函数调用顺序: 普通函数 > 具体化模板函数 > 常规模板函数
可以使用Swap<>(),即空模板参数强制使用函数模板,即
普通函数 > 具体化模板函数 > 常规模板函数
如果实参在普通函数里没有对应的类型(在不考虑隐式转换的前提下),那么会优先寻找模板函数,看有没有对应的实现; 比如Swap('a', 'b')是<char>类型的实参,而普通函数是int类型的,没有模板函数时,会调用普通函数(此时存在由char到int的隐式转换);有函数模板时,则实例化并调用<char>类型的Swap函数。
二、模板函数的实现和定义分离
之前开发过程中遇到了很多这样的问题,书上说模板函数的声明和实现都放在头文件中比较好,然后网上很多文章也说了怎么实现这两者的拆分balabala,但是在实际开发中总还是会因为这个问题遇到编译报错或者运行报错等等问题。这次一次性记录清楚,形成自己的一个编写”规范“:
1.全部声明和定义都在一个cpp文件中
这种情况开发中几乎不可能出现,顶多是学习过程中会偶尔有,这种情况所有的声明和定义都在一起,一般不会报错,不再赘述。
2.声明和定义分别放在头文件.h和源文件.cpp中
(1)普通函数
声明放在.h中,定义放在.cpp中。 普通函数嘛,没什么好说的,一般都是这么搞
(2)常规函数模板
如果把声明放在.h中,定义放在.cpp中,编译不出错,运行时提示”无法解析的外部命令“; ×
如果把声明和定义都放在.h中,编译正常,运行正常; √
(3)具体化模板函数
当常规模板函数的声明和定义全在.h中时,当具体化模板函数的声明和定义也全在.h中,,编译正常,运行时提示”找到一个或多个重定义的符号“;×
当常规模板函数的声明和定义全在.h中时,当具体化模板函数的声明在.h中,当具体化模板函数的定义在.cpp中时,编译正常,运行正常;√
三、总结
1. 实例化是编译器根据调用函数的实参,自动生成一个对应版本的函数定义的过程;具体化,是人为编写一个函数定义,让编译器匹配到并调用该定义而不是寻找函数模板。
2.常规函数和具体化的模板函数,在.h中声明,在.cpp中定义; 常规函数模板的声明和定义都在.h中,这种方法就是可行的,这么搞就行了!
如果这篇文章帮到了你,记得点赞!