template
关键字。模版特性也可以成为参数化特性。
A template is a cookie-cutter that specifies how to cut cookies that all look pretty much the same (although the cookies can be made of various kinds of dough, they’ll all have the same basic shape). In the same way, a class template is a cookie cutter for a description of how to build a family of classes that all look basically the same, and a function template describes how to build a family of similar looking functions.
Class templates are often used to build type safe containers (although this only scratches the surface for how they can be used).
其实从 template 这个英文单词上我们就可以理解其作用,我们希望编写好一个函数或者类后,可以服用其内容,因此将可以定制化的东西参数化,这样可以提高代码复用,是继承机制之外的另一种抽象。而且模版类还提供了安全性。
考虑以下交换两个变量值的函数,我们希望可以将该函数不仅支持交换int,还可以交换其他类型的参数。那么可以定义以下的函数模版。
void swap(int &a, int &b);
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
// prototype
template<typename T>
void swap(T &a, T &b);
template<typename T>
void swap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
可以通过以下的调用形式,这样达到了函数复用的目的
int main()
{
int i,j; /*...*/ swap(i,j); // Instantiates a swap for int
float a,b; /*...*/ swap(a,b); // Instantiates a swap for float
char c,d; /*...*/ swap(c,d); // Instantiates a swap for char
std::string s,t; /*...*/ swap(s,t); // Instantiates a swap for std::string
// ...
}
关于上述代码,我们有几点需要注意
class Printer {
public:
template<typename T>
void print(const T& t) {
cout << t <<endl;
}
};
Printer p;
p.print<const char*>("abc"); //打印abc
为什么成员函数模板不能是虚函数(virtual)?
这是因为c++ compiler在parse一个类的时候就要确定vtable的大小,如果允许一个虚函数是模板函数,那么compiler就需要在parse这个类之前扫描所有的代码,找出这个模板成员函数的调用(实例化),然后才能确定vtable的大小,而显然这是不可行的,除非改变当前compiler的工作机制。
这一部分是比较绕也是重点的部分。
先来看具体化,具体化其实也是C++在发展过程中增加的特性,目的就是为了解决模板函数无法处理某些类型的局限性,比如无法将一个数组赋值给另一个数组。对于这种情况C++提供了语法支持,可以为特定类型提供具体化的模板定义。
C++98采用了如下的标准:
代码示例:
#include
template<typename T>
void Swap(T &a, T &b);
struct job {
int size;
};
// explict specialization
template<>
void Swap<job>(job &j1, job &j2);
int main() {
using namespace std;
int i = 10, j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
cout << "Using compiler-generated int Swapper:\n";
Swap(i, j); // generates void Swap(int &, int &)
cout << "Now i, j = " << i << ", " << j << ".\n";
job alice = {1};
job bob = {2};
cout << "Before job Swapping:\n";
cout << "alice's size: " << alice.size << endl;
cout << "bob's size: " << bob.size << endl;
Swap(alice, bob); // uses void Swap(job &, job &)
cout << "After job Swapping:\n";
cout << "alice's size: " << alice.size << endl;
cout << "bob's size: " << bob.size << endl;
return 0;
}
template<typename T>
void Swap(T &a, T &b) {
T temp;
temp = a;
a = b;
b = temp;
}
template<>
void Swap<job>(job &j1, job &j2) {
int temp = j1.size;
j1.size = j2.size;
j2.size = temp;
}
output
i, j = 10, 20.
Using compiler-generated int Swapper:
Now i, j = 20, 10.
Before job Swapping:
alice's size: 1
bob's size: 2
After job Swapping:
alice's size: 2
bob's size: 1
首先在代码中包含函数模板本身并不生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation),模板并非函数定义,但是上述使用 int 的模板实例是函数定义。像 swap(i, j);
的调用方式成为隐式实例化(implicit instantiation)。
最初编译器只能隐式实例化,现在支持显示实例化,这意味着可以直接命令编译器创建特定的实例。
template void Swap<int>(int, int);
我们要区分显示具体化
template <> void Swap<int>(int, int);
template <> void Swap(int, int);
在语法上区分,具体化有 <>
,而实例化没有,这也很好记忆,具体化在函数原型和函数定义的代码部分, 这部分代码都需要 <>
来表明范性。
还可以在程序中使用函数来创建显示实例化,如以下代码
template<typename T>
T add(T a, T b) {}
int m = 1;
double x = 1.1;
cout << add<double>(x, m); // explicit instantiation
上述代码 add
表明了强制使用 double 实例化,因此 m 将被强制转换为 double 类型。
C++11 引入了 decltype
, 主要解决了类型转换的问题,比如如下代码
template<typename T1, typename T2>
void f(T1 x, T2 y) {
T z = x + y; // z的类型不好确定
}
使用 decltype
关键字后就可以使用 decltype(x+y) z.= z + y;
在定义模板时也需要用到C++11新特性,后置返回类型,比如无法确定返回值类型时。
后置返回类型语法;
double h(int x, float y); // common function
auto h(int x, float y) -> double; // trailing return type
代码示例
template<typename T1, typename T2>
auto h(T1 x, T2 y)-> decltype(x + y) {
return x + y;
}
模版与模版之间,普通函数与模版之间都可以重载,我们可以观察如下函数原型
template<typename T>
void Swap(T &a, T &b); // 1
template<typename T>
void Swap(T *a, T *b, int n); // 2
void Swap(int &a, int &b); // 3
int main() {
int i = 1;
int j = 2;
Swap(i, j); // use 3
int ar1[2] = {0, 0};
int ar2[2] = {1, 1};
Swap(ar1, ar2, 2); // use 1
}
重载时的优先级为: