C++模板与泛型编程:模板函数

定义模板

假设我们希望编写一个函数来比较两个值,并指出第一个值与第二个的大小关系。在实际中,我们可能需要定义多个函数,每个函数比较一种给定类型的值。我们的代码可能是这样的:

int compare(const string &v1,const string &v2) {
    if(v1 < v2) return -1;
    if(v2 < v1) return 1;
    return 0;
}
int compare(const double &v1,const double &v2) {
    if(v1 < v2) return -1;
    if(v2 < v1) return 1;
    return 0;
}

其实我们可以发现,上面两个函数除了参数的类型,其他的都是完全相同的。何况,实际比较中,不可能是只出现上述两种类型的比较。所以我们可能编写很多这样几乎一模一样的函数。这十分繁琐,而且容易出错。

函数模板

​ 我们可以定义一个通用的函数模板,而不是为每个类型都定义一个函数。一个函数模板就是一个公式,用来生成针对类型的函数版本。compare 的模板版本可能是下面这样:

template <typename T>
int compare(const T &v1,const T &v2) {
    if(v1 < v2) return -1;
    if(v2 < v1) return 1;
    return 0;
}

模版的定义由关键字 template 开始,后跟一个模板参数列表,这是由逗号分隔的一个或多个模板参数的列表。用尖括号包围起来。

在模板定义中,模板参数列表不能为空

​ 模板参数列表的作用很像函数参数列表。模板参数表示在类或函数定义中用到的类型或值。当使用模板时,我们(隐式或显式地)指定模板实参,将其绑定到模板参数上。

​ 我们的 compare 声明了一个名为 T 的类型参数。在 compare 中,我们用 T 表示一个类型。而 T 的实际类型会在编译时根据 compare 的使用情况来确定。

实例化函数模板

当我们调用一个函数模板时,编译器通常用函数实参来为我们推断模板实参。如:

cout << compare(1,0) << endl;

实参类型是 int。编译器会推断出模板实参为 int,并将它绑定到模板参数 T。当编译器实例化一个模板时,使用实际的模板实参代替对于的模板参数来创建出模板的一个新“实例”。

​ 如上述调用,会实例化出 int compare(const int&,const int&);

​ 对编译器生成的版本通常被称为模板的实例

模板类型参数

​ 我们的 compare 有一个模板类型参数。一般来说,我们可以将类型参数看做类型说明符,就像内置类型或类类型说明符一样使用。特别是,类型参数可以用来指定返回类型或函数的参数列表,以及在函数体内用于变量声明或类型转换

template<typename T>
T foo(T* p) {
    T tmp = *p;
    // ...
    return tmp;
}

模板类型参数前必须使用关键字 typename 或 class。两者在定义模板参数时没有任何区别。

非类型模板参数

​ 除了定义类型参数,还可以在模板中定义非类型参数。一个非类型参数代表一个值而非一个类型。用特定的类型名来指定非类型参数即可。

当一个模板实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替这些值必须是常量表达式,从而允许编译器在编译时实例化模板。

​ 如,我们可以编写一个 compare 处理字符串字面常量。字面常量是 const char 的数组。同时,数组不能够被拷贝,所以我们声明为数组的引用。

template<unsigned N, unsigned M>
int compare(const char (&p1)[N],const char (&p2)[M]) {
    return strcmp(p1,p2);
}

当我们调用这个 compare 时:

compare("hi", "hhh");

编译器会使用字母常量的大小来代替 N 和 M,从而实例化模板。而且需要注意,编译器会插入 ‘\0’ 作为终结符。所以编译器实际实例化出的是:

int compare(const char (&p1)[3],const char (&p2)[4]);
inline 和 constexpr 的函数模板

​ 函数模板可以声明为 inline 或 constexpr 的,这两个说明符放在模板参数列表之后,返回类型之前:

template<typename T> inline T min(const T&,const T&);
编写类型无关的代码

​ 我们的 compare 函数虽然简单,但它说明了编写泛型代码的两种重要原则:

  • 模板中的函数参数是 const 的引用。
  • 函数体中的条件判断仅使用 < 比较运算。

通过将函数参数设定为 const 的引用,我们保证了函数可以用于不能拷贝的类型。

​ 你可能认为既使用 < 有使用 > 进行比较操作会更自然,即:

if(v1 < v2) return -1;
if(v1 > v2) return 1;
return 0;

但这样,我们使用的类型就必须支持 < 和 >,而我们只使用 <,compare 的可用性就可能会更高。

​ 实际上,如果我们真的关心类型无关和可移植性,我们应该使用 less 来定义我们的函数:

template <typename T>
int compare(const T &v1,const T &v2) {
    if(less<T>(v1,v2)) return -1;
    if(less<T>(v2,v1)) return 1;
    return 0;
}

当然,这只是举个例子,虽然 less<T> 默认使用的就是 <。

模板程序应该尽量减少对实参类型的要求

模板编译

​ 当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。当我们使用(不是定义)模板时,编译器才生成代码。

​ 与类有一点不同,类可以将类定义和函数声明放在头文件,普通函数和类的成员的定义放在源文件。但是模板不可以,模板头文件通常既包含声明也包括定义。

大多数编译错误在实例化期间报告

​ 对于模板的使用,编译器通常会在三个阶段报告错误:

  • 第一阶段是编译模板本身时。如变量名错误,分号忘了等。
  • 第二阶段是编译器遇到模板使用时。如检查实参数目是否正确,参数类型是否匹配等。
  • 第三阶段是模板实例化时,只有这个阶段才能发现类型相关的错误。依赖于如何实例化。

我们知道,compare 函数中使用了 < 运算符进行比较。如果我们实例化模板时,其参数没有定义 <,所以显然是不能调用的。

保证传递给模板的实参支持模板所要求的操作,以及这些操作在模板中能正确工作,是调用者的责任。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值