14-4类模板

类模板

14.4.1定义类模板

模板类的开头如下

template <类型参数表>
class 类模板名{
成员函数和成员变量
};

类型参数表的写法如下:

class类型参数1, class类型参数2, ...

例如

template <class Type>	//这个Type可以用其它变量名替代(T)

或者

template <typename Type>

同时,用模板成员函数来替代原有的类方法,每个函数头都以相同的模板声明开头

template <class Type>

例如

template <class T>
bool Stack<T>::push(const T & item)
{
...
}

注:不能将模板成员函数放在独立的文件中实现,因为它们不是函数,不能单独编译,必须和特定的模板实例化请求一起使用。

案例

#pragma once

template <class T> //模板类声明
class Stack
{
private:
enum {MAX = 10};
T item[MAX];
int top;
public:
Stack();
bool isempty();
bool isfull();
bool push(const T & item);
bool pop(T & item);
};

template <class T>
Stack<T>::Stack()
{
top=0;
}

template <class T>
bool Stack<T>::isempty()
{
return top==0;
}

template <class T>
bool Stack<T>::isfull()
{
return top==MAX;
}

template <class T>
bool Stack<T>::push(const T & item)
{
if(top < MAX)
{
items[top++]=item;
return true;
}
else
return flase;
}

template <class T>
bool Stack<T>::pop(T & item)
{
if(top>0)
{
item = items[--top];
return true;
}
else
return flase;
}

14.4.2使用模板类

程序包含模板并不能生成模板类,而必须请求实例化。

因此,需要声明一个类型为模板类的对象。

Stack<int> kernels;
Stack<string> colonels;

上述声明编译器将使用模板生成两个独立的类声明和两组独立的类方法。

并将<>中的关键字替代原有的Type

**注:**必须显式地提供所需的类型

14.4.3深入讨论模板类

对于不同类型的参数,模板的实现效果也不同。一些实现并不正确。

1.不正确地使用指针栈
使用char*

编译时通过,但由于没有创建用于保存字符串的空间导致在读入数据时会崩溃

使用字符数组char [num]

使用数组名不能作为引用来使用,所以会出现问题

使用new来分配char*空间

只有一个变量导致压栈和出栈时的地址一样,没有效果

2.正确使用指针栈

方法之一是让调用程序创建一个指针数组,其中每个指针都指向一个分配好空间的字符串,再将这些指针放入栈。

14.4.4数组模板示例和非类型参数

讨论一些非类型参数以及如何使用数组来处理继承族

#pragma once

#include<iostream>
#include<cstdlib>

template <class T, int n>
class ArrayTP
{
private:
T ar[n];
public:
ArrayTP() {};
explicit ArrayTP(const T & v); //防止隐式转换
virtual T & operator[](int i);
virtual T operator[](int i) const;
};

template <class T, int n>
ArrayTP<T,n>::ArrayTP(const T & v) //初始化n个相同元素
{
for(int i=0;i<n;i++)
ar[i]=v;
}

template <class T, int n>
T & ArrayTP<T,n>::operator[](int i)
{
if(i<0||i>=n) //防止数组元素引用超出限制
{
std::cerr<<"Error in array limits: "<<i
<<"is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}

template <class T, int n>
T ArrayTP<T,n>::operator[](int i) const
{
if(i<0||i>=n) //防止数组元素引用超出限制
{
std::cerr<<"Error in array limits: "<<i
<<"is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}
说明
template <class T, int n>

关键字class指出T为类型参数,而int指出n为非类型参数(表达式参数)。

在实例化时,可以这样使用

ArrayTP<double, 12> eggweight;
//编译器将使用double替换T,使用12替换int

限制

1.表达式参数可以是整型、枚举、引用和指针。其余都不合法

2.模板代码不能修改参数的值,也不能使用参数的地址。

3.实例化模板时,表达式参数的值必须是常数表达式

14.4.5模板多功能性

模板类可以使用另一个模板类作为参数

Array <Stack<int>> asi;
//一个元素为栈的数组
1.递归使用模板

例如,二维数组可以这样使用

ArrayTP <ArrayTP<int,5>,10> twodee;
//10*5的数组
2.使用多个类型参数

模板可以包含多个类型参数

template <class T1,class T2>
3.默认类型模板参数

可以为类型参数提供默认值

template <class T1,class T2 = int>

注:虽然可以给类模板类型参数提供默认值,但不能为函数模板参数提供默认值。但对两者的非类型参数,都能提供默认值。

14.4.6模板的具体化

模板以泛型描述类,具体化是使用具体的类型生成类声明

1.隐式实例化

声明一个或多个对象,指出所需的类型。

注:编译器在需要对象之前不会生成类的隐式实例化

ArrayTP<double, 12> * pt;		//未生成
py = new ArrayTP <double, 12>; //生成
2.显式实例化

使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。

template class ArrayTP<string, 100>;
//该声明将ArrayTP<string, 100>声明成一个类

注:该声明必须在模板定义的名称空间中

3.显式具体化

显式具体化是特定类型的定义。某些时候,可能需要为特殊类型实例化时,对模板进行修改,使其行为不同。

案例

假设已经为用于排序之后的数组的类定义了一个模板

template <typename T>
class SortedArray
{
...
}

另外假设模板使用>运算符进行比较。

当T是整型时,可以做到。

但当T是字符串时,就会出现问题。模板会正常工作,但按照字符串的地址排序,与我们所想的不符。

此时采用具体化版本

具体化格式:

template <> class Classname<specialized-type-name> {...};

所以我们将其具体化为如下专供模板:

template <> class SortArray<const char *>
{
...
}
4.部分具体化

C++允许部分具体化,即部分限制模板的通用性

案例1

部分具体化的基础

//通用模板
template <class T1, class T2> class Pair {...};
//部分具体化模板
template <class T1> class Pair<T1,int> {...};

部分具体化将template后的<>中的具体化参数去除,并在类名后加入

如果参数全部去除,则为显式具体化

template <> class Pair<int,int> {...};
//显式具体化

案例2

指针形式

可以使用指针来提供特殊版本的部分具体化

template <class T>		//通用版本
class Feeb {...};

template <class T*> //指针版本
class Feeb {...};

这样,凡是提供的是指针参数,都优先采用指针版本来具体化模板。

Feed<char *> fb;	//指针模板

案例3

部分具体化还能用来设置各种限制

//通用版本
template <class T1,class T2,class T3> class Trio {...};
//将T3和T2保持一致的版本
template <class T1,class T2> class Trio<T1,T2,T2> {...};
//将T3和T2设置为T1*的版本
template <class T1> class Trio<T1,T1*,T1*> {...};

14.4.7成员模板

模板可以用作类,模板类等的成员

案例

#include<iostream>
using std::cout;
using std::endl;
template <typename T>
class bate
{
private:
template <typename V> //创建模板类声明
class hold
{
private:
V val;
public:
hold(V v=0) : val(v) {}
void show() const {cout << val << endl;}
V Value() const {return val;}
};

hold<T> q;
hold<int> n;
public:
beta(T t, int i) : q(t), n(i) {}
template<typename U> //模板方法
U blab(U u, T t) { return (n.Value() + q.Value() * u / t);}
void Show() const {q.show(); n.show();}
}

14.4.8将模板用作参数

模板除了包含类型参数和非类型参数,还可以包含本身就是模板的参数

template <template <typename T> class Thing>
class Crab
//这是一个模板的声明,要求参数是一个模板

其中template <typename T> class是类型,Thing是参数

这意味着假设有如下声明:

Crab<King> legs;

必须使得King是一个模板类,且与模板参数Thing匹配

案例

#include<iostream>
#include"stacktp.h>

template <template <typename T> class Thing>
class Crab
{
private:
Thing<int> s1;
Thing<double> s2;
public:
Crab() {}
bool push(int a, double x) { return s1.push(a) && s2.push(x); }
bool pop(int & a, double & x) { return s1.pop(a) && s2.pop(x); }
};

14.4.9模板类和友元

1.非模板友元

非模板友元函数是所有实例化的友元,不能通过对象调用,也没有对象参数。

可以访问:

1.全局对象

2.使用全局指针访问非全局对象

3.创建自己的对象

4.访问独立于对象的模板类的静态数据成员

注:为友元函数提供模板类参数,必须指明具体化。

案例

template <class T>
class HasFriend
{
friend void report(HasFriend<T> &);
}

对于每个具体化,都会生成一份对应的友元函数

注:report()本身不是模板函数,而只是使用了一个模板作为参数,也就意味着必须在类声明以外为需要的使用的友元定义显式具体化。

void report(HasFriend<short> &) {...};
void report(HasFriend<int> &) {...};
//因为友元在类中的声明仅仅是说明其拥有具有访问类的权利,但并不是真正在类中被声明,因此不在模板类的名称空间中,不能享有其可变参数的特性,因而需要在外面对可能被生成的类型创建函数的各种版本重载。
2.约束模板友元函数

对上述方法进行改进,使友元函数本身成为模板的方法,以便不用创建各个版本的重载友元函数。

“约束”是指这个友元函数与类模板的参数保持一致,被类模板“约束”住了。

案例如下

要使类的每一个具体化都与都获得与友元匹配的具体化,有三步:

1.在类定义前面声明每个模板函数

template <typename T> void counts();
template <typename T> void report(T &);
//这意味着这个函数首先是个模板函数

2.在函数中再次将模板声明为友元

template <typename TT>
class HasFriendT
{
...
friend void counts<TT>();
friend void report<>(HasFriend<TT> &); //<>指出这是模板具体化
}

//这意味着这个模板函数在模板类中被具体化了参数,并被列为友元。再将被具体化的参数传给模板函数声明,模板自动生成符合类模板参数的模板函数版本,定义也自动更新。
3.为友元提供模板定义
template <typename T> 
void counts()
{
...
}
//注意,提供定义中的参数应当是模板函数声明中的参数,而不是类模板的参数。
//这表明类模板只是调用了函数模板,把类模板的参数传给函数模板的参数,以此调用函数模板生成符合类的友元函数。

3.非约束模板友元函数

“非约束”是指友元函数是模板函数,但不被类模板的具体化所约束,而是在类模板具体化之后依旧保持模板状态,能够根据具体调用时的参数再具体化。

创建方法

在类内部声明友元的函数模板,在外部实现定义,不要再在外面做提前模板声明。

14.4.10 使用模板别名

1.使用typedef
typedef std::array<int,12> arri;
typedef std::array<double,12> arrd;
typedef std::array<std::string,12> arrs;
2.使用using= (同样可用于正常的定义别名)
using arri = std::array<int,12>;