简介
本文中的内容主要参考C++ Primer Plus(第6版)中文版
一书中的内容。该篇博客主要根据自己使用模板的经验,结合书中的内容,总结函数模板的原理和使用方法。
函数模板概述
函数模板是通用的函数描述,它使用泛型来定义函数,然后可以使用具体的类型替换泛型。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。首先函数模板的定义参考下面代码:
template <typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
从上述代码中可以看出,关键字template
和typename
(>=C++98)是必须的。
提示:除非需要考虑向后兼容的问题(即老版本的库),否则推荐使用关键字typename
。
需要注意的是函数模板并不能够缩短可执行程序。当在代码中调用函数模板时,编译器仍然在程序中生成该函数相应类型的版本,具体参考以下代码:
int i = 10, j = 20;
Swap(i,j)
double x = 24.5;
double y = 81.7;
Swap(x,y); // generates void Swap(double &, double &)
当编译器编译上述代码时,最终将生成int
和double
版本的Swap
函数,,最终的代码不包含任何模板,而只包含了未程序生成的实际函数。
void Swap(int &a, int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
void Swap(double &a, double &b)
{
double temp;
temp = a;
a = b;
b = temp;
}
函数模板重载
上一小节中给出的模板无法交换数组类型,原模板的特征标为(T &, T &),而新模板的特征标为(T [], T[], int),新模板中最后一个参数为具体类型int
,而不是泛型。并非所有的模板参数都必须是模板参数类型。
template <typename T> // original template
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template <typename T> // new template
void Swap(T *a, T *b, int n)
{
T temp;
for (int i = 0; i < n; i++)
{
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
同常规重载一样,被重载的模板的函数特征标必须不同(不同返回值不能实现重载)。
函数模板的局限性
template <typename T>
void f(T a, T b)
{ }
上述模板中若函数体为a = b
,但如果T
为数组,则上述代码不是所希望的操作;
同理若函数体为if (a > b)
,但如果T
为结构体,这种假设同样无法成立,结构体比较大小不是我们希望的操作。
总之,编写的模板函数很可能无法处理某些类型。为实现两个包含位置坐标的结构相加是有意义的,有两种解决方案:
1. 重载运算符+,以便能够用于特定的结构或类;
2. 另一种解决方案就是为特性类型提供具体化的模板定义。
因为本文主要是介绍模板,所以后面我们将介绍第二种解决方案。
显示具体化
- 具体化规则
- 对于给定的函数,可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载版本;
- 显示具体化的原型和定义应以
template<>
打头,并通过名称来指出类型; - 具体化优先于常规模板,而非模板函数优先于具体化和常规模板。
- 显示具体化
上述已经给出具体化的规则。下面以具体的例子说明模板的显示具体化。
template <typename T>
void Swap(T &a, T &b);
struct job
{
char name[40];
double salary;
int floor;
};
// explicit specialization
template <> void Swap<job>(job &j1, job &j2);
模板显示具体化中由于函数的参数类型已经表明具体类型的具体化,因此显示具体化的简化模式如下代码所示:
template <> void Swap(job &j1, job &j2);
既可以省略Swap<job>
中的job。
实例化
编译器使用模板为特定类型生成函数定义时,得到的是模板实例(Instantiation)。模板本身并非函数定义,但使用具体类型的模板实例是函数定义,这种实例化的方式被称为隐式实例化(Implicit Instantiation)。
现在C++还允许显示实例化(Explicit Instantiation),即可以直接命令编译器创建特定的实例,语法如下所示:
template void Swap<int>(int &j1, int &j2); // explicit instantiation
该声明的意思是使用Swap()
模板生成int
类型的函数定义。
显示实例化的使用方式有两种:
-
在程序中使用函数来创建显示实例化
int x, y; std::cout << Swap<int>(x, y) << std::endl; // explicit instantiation
-
通过声明的方式创建显示实例化
tenplate void Swap<int>(int&, int&); // explicit instantiation int x, y; Swap(x, y); // 使用上述显示实例化
显示具体化与显示实例化的区别就是显示具体化需要针对特定的类型显示地定义函数体。
模板函数的发展
-
使用关键字decltype在函数内部推导类型
template<class T1, class T2> void ft(T1 x, T2 y) { decltype(x+y) xpy = x + y; }
-
后置返回类型(trailing return type)
template<class T1, class T2> auto ft(T1 x, T2 y) -> decltype(x + y) { return x + y; }
函数模板和模板函数的区别
函数模板定义
函数的重载能够实现一个函数名多用,将实现相同或者相似功能的函数用同一个函数名来定义,但是在程序中仍然要分别定义每一个函数。为进一步简化,C++提供了函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表,此通用函数即为函数模板。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。
模板函数定义
为函数模板传参,根据实际传入的参数类型生成的一个重载函数即为模板函数,它是函数模板的一次实例化。
举例说明
template<typename T>
bool compare(T a,T b)
{
cout << "template compare" << endl;
return a>b;
}
上面的代码部分是函数的模板,所谓的模板,就是以后的函数实例化都是根据这个模板进行的。
int main()
{
compare<int>(10,20);
compare<double>(12.3,78,9);
return 0;
}
这就是实例化的函数:模板函数,根据模板写出来的函数。