C++知识点-函数模版

本文深入解析C++模板,介绍函数模板如何实现类型通用,类模板如何使用与实例化,以及模板函数的重载规则。重点讲解了实例化与具体化概念,包括为何成员函数模板不支持虚函数,以及C++11中decltype和后置返回类型的运用。
摘要由CSDN通过智能技术生成


作为一门面向对象的语言,C++也支持范型编程,而这种语言特性在C++中就对应了 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
  // ...
}

关于上述代码,我们有几点需要注意

  • 上述用不同的参数调用4次swap函数,那么编译器就会对应生成4个不同的函数定义。
  • 最终的代码不会renew模版,而只包含了为程序生产的实际函数

类成员函数模板

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采用了如下的标准:

  • 对于给定的函数名,可以有非模板函数,模板函数和显示具体化模板函数以及它们的重载版本
  • 显示具体化的原型和定义应已 template<> 打头,并通过名称指出类型
  • 具体化优先于常规模板,而非模板函数优先于具体化和常规模板

代码示例:

#include <iostream>

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>(x, m) 表明了强制使用 double 实例化,因此 m 将被强制转换为 double 类型。

关键字 decltype

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
}

重载时的优先级为:

  • 普通函数
  • 特殊模板(限定了T的形式的,指针、引用、容器等)
  • 普通模板(对T没有任何限制的)

Reference

  1. C++ Template 基础篇(一):函数模板
  2. Templates
  3. 《C++ Primer Plus》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值