使用场景
我们先来看下面一个例子:
//一个简单的比较函数
int compare(const string &v1, const string &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int conpare(const double &v1, const double &v2)
{
if(v1 < v2) return -1;
if(v2 < v1) return 1;
return 0;
}
上面在重载函数,两个函数唯一的不同就是参数的类型。
如果类型比较多的话,就会变得非常麻烦,每一种都要定义一次。
如果希望能在用户提供类型上使用此函数,这种策略就会完全失效。
函数模板
模板的定义就是下面的格式:
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
以template 关键字开始,后跟一个模板参数列表。用尖括号包起来。每个类型前都要加 typename 或者 class 关键字,两个关键字是等价的,只是typename看起来要更清晰一点。
实例化函数模板
编辑器用函数实参来为我们推断模板实参。用实参的类型来确定绑定到模板参数T的类型。
//实例化出 int compare(const int&, const int&)
cout << compare(1, 0) << end; // T为int
//实例化出 int compare(const vector<int>&, const vector<int>&)
vector<int> v1 {1, 2, 3} , v2 {4, 5, 6};
cout<< compare(v1, v2) <<endl; //T为vector<int>
编辑器生成的版本就叫做模板的实例。
模板类型参数与非类型模板参数
模板类型参数就是前面代码里的 T。可以看做类型说明符一样来使用。
非类型参数表示一个值而非一个类型。我们使用特定的类型名而不是关键字class或typename 来指定。还是先看代码:
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
return strcmp(p1,p2);
}
然后来调用这个模板函数
compare("hi", "world");
编辑器会使用字面常量的大小来替代N和M,以实例化模板。
编辑器会在一个字符串字面常量的末尾插入一个空字符来作为终结符。因此实例化出如下版本:
int compare(const char (&p1)[3] ,const char (&p2)[6])
需要记住,模板费类型参数是一个常量值,在需要常量表达式的地方,可以使用非类型参数,例如,指定数组大小。
函数模板也可是生命为inline 或 constexpr 的,就像非模板函数那样:
template <typename T> inline T min(const T&, const T&);
要编写类型无关的代码
编写泛型代码有两个重要原则:
1、模板中的函数参数是const的引用。
2、函数体中的条件判断仅使用 < 比较运算符。
第一条是为了保证函数可以应用在不能拷贝的类型。像内置类型、大多标准库类型都是可以拷贝的,但也会存在不能拷贝的类型。而且在处理大对象的时候,这种策略也使函数运行的更快。
编写代码只使用 < 运算符,降低了compare函数对要处理的类型的要求、这些类型必须支持 < ,但不必支持 > 。
模板程序应该尽量减少对实参类型的要求。
模板编译
只有实例化出模板的一个特定版本时,编译器才会生成代码。这就导致了大多数编译错误在实例化期间报告。
模板为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此模板的头文件中通常既包括声明也包括定义。