第十四章-类模板(下)//成员模板//模板类和友元//C++11模板名特性

文章目录

      • 14.4.6 模板的具体化
      • 14.4.7 成员模板
      • 14.4.8 将模板用作参数
      • 14.4.9 模板类和友元
      • 14.4.10 模板别名(C++11)

14.4.6 模板的具体化

类模板与函数模板相似,可以有隐式实例化、显式实例化和显式具体化,统称具体化。模板以泛型的方式描述类,而具体化使用具体的类型生成类声明。

1.隐式实例化
目前本章所有模板示例都是用的此方法:它们声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义:

ArrayTP<int, 100> stuff;

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

ArrayTP<double, 30>* pt; //指针,还不需要对象
pt = new ArrayTP<double, 30>; //现在需要对象了

第二句导致编译器生成类定义,并根据定义创建对象。

2.显式实例化
当关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。声明必须位于模板定义所在的名称空间中。比如下面的声明将ArrayTP声明为一个类:

template class ArrayTP<string, 100>;

这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成具体化。

3.显示具体化
显式具体化是特定类型(用于替换模板中的泛型)的定义。有时候可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。在这种情况下,可以创建显式具体化。
举例:假设已经为用于表示排序后数组的类(元素在加入时被排序)定义了一个模板:

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

另外,假设模板使用>运算符来对值进行比较。对于数字这会管用;但如果T表示一种类,则只要定义T::operator>()方法,也会管用;但如果const char*表示的字符串,这就不管用了。实际上模板使可以正常工作的,但是字符串将按照地址(按字母顺序)排序。这要求类定义使用strcmp(),而不是>来比较。这时可以提供一个显式模板具体化,这将采用为具体类型定义的模板,而不是泛型定义的模板。当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本。
具体化模板定义格式:

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

要使用新的表示法提供一个专供const char*类型使用的SortedArray模板,可以使用类似于下面的代码:

template <> class SortArray<const char char*>
{
	...
};

其中的实现代码将使用strcmp()来比较数组值。
现在当请求const char*类型的SortArray模板时,编译器将使用上述专用的定义而不是通用模板的定义:

SortedArray<int> scores;
SortedArray<const char*> dates;

4.部分具体化
C++还允许部分具体化,即部分限制模板的通用性。
不如部分具体化可以给类型参数之一指定具体的类型:

template <class T1, class T2> class Pair {...};
template <class T1> class Pair<T1, int> {...};

关键字template后面的<>声明的是没有被具体化的类型参数。因此上述第二个声明将T2具体化为int,但T1保持不变。注意,如果指定所有的类型,则<>内将为空,这将导致显式具体化:

template <> class Pair<int, int> {...};

如果有多个模板可供选择,编译器将使用具体化程度最高的版本模板

Pair<double, double> p1; //使用通用模板
Pair<double, int> p2; //使用Pair部分具体化
Pair<int, int> p3; //使用Pair显式具体化

也可以通过为指针提供特殊版本来部分具体化现有的模板:

template<class T> //通用版本
class Feeb {...};
template<class T*> //指针部分具体化版本
class Feeb {...};

如果提供的类型不是指针,则编译器将使用通用版本;如果提供的是指针,则编译器将使用指针的具体化版本:

Feeb<char> fb1; //使用通用Feeb模板,T是char
Feeb<char*> fb2; //使用T*具体化,T还是char

如果没有进行部分具体化,则第二个声明将使用通用模板,则T转换为char*。如果进行了部分具体化,第二个声明将使用具体化模板,T转换为char。
比分具体化特性使得能够设置各种限制,比如:

//通用模板
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*> {...};

有了上述声明后,编译器会做出如下抉择:

Trio<int, short, char*> t1; //使用通用模板
Trio<int, short> t2; //使用Trio
Trio<char, char*char*> t3 //使用Trio

14.4.7 成员模板

模板可用作结构、类或模板类的成员。要完全实现STL的设计,必须使用这项特性。接下来实现一个简短的模板类示例,该模板类将另一个模板类和模板函数作为其成员:

#include 
using std::cout;
using std::endl;
template <typename T>
class beta
{
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(); }
};
int main()
{
	beta<double> guy(3.5, 3);
	cout << "T was set to double\n";
	guy.Show();
	cout << "V was set to T, which is double, then V was set to int\n";
	cout << guy.blab(10, 2.3) << endl;
	cout << "U was set to int\n";
	cout << guy.blab(10.0, 2.3) << endl;
	cout << "U was set to double\n";
	cout << "Done\n";
	return 0;
}

hold模板是在私有部分声明的,因此只能在beta类中访问它。beta类使用hold模板声明了两个数据成员:

beta<T> q;
beta<int> n;

n是基于int类型的hold对象,而q是基于T类型(beta模板参数)的hold对象。在main()中下述声明使得T表示的是double,因此q类型为hold:

beta<double> guy(3.5, 3);

blad()方法的U类型由该方法被调用时的参数值显式决定,T类型由对象的实例化类型确定。在这个例子中,guy的类型将T的类型设置为double,而下述方法调用的第一个参数将U的类型设置为int(10对应的类型):

cout << guy.blab(10, 2.5) << endl;

因此虽然混合类型将引起的自动类型转换导致blab()中的计算以double类型进行,但返回值的类型为U(即int),因此它被截断为28,如下面程序所示:

T was set to double
3.5
3
V was set to T, which is double, then V was set to int
28
U was set to int
28.2609
U was set to double
Done

主要调用guy.blab()时,使用10.0代替了10,因此U被设置为double,这使得返回类型为double,因此输出28.2608.
正如前面说明的,guy对象的声明将第二个参数的类型设置为double。与第一个参数不同的是,第二个参数的类型不是由函数调用设置的。比如下面的语句仍将blah()实现为blah(int, double),并根据常规函数原型规则将3转换为double

cout << guy.blah(10, 3) << endl;

可在beta模板中声明hold类和blah方法,并在beta模板的外面定义它们。但是有一些老编译器不接受模板成员,而另一些编译器接受模板成员,但不接受类外面定义。然而如果所用的编译器接受类外面定义,则在beta模板之外定义模板方法的代码如下:

template <typename T>
class beta
{
private:
	template <typename V>
	class hold;
	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);
	void Show() const { q.show(); n.show();}
};
template <typename T>
 template<typename V>
  class beta<T>::hold
  {
  private:
  	V val;
  public:
  	hold(V v = 0) : val(v) {}
  	void show() const { std::cout << val << std::endl; }
  	V Value() const { return val; }
  };
template <typename T>
 template <typename V>
  U beta<T>::blab(U u, T t)
  {
  	return (n.Value() + q.Value()) * u / t;
  }

上述定义将T、V和U用作模板参数。因为模板是嵌套的,因此必须使用这种语法:

template <typename T>
 template <typename V>

而不能是这种:

template<typename T, typename V>

定义还指出hold和blab是beta< T>类成员,这是通过使用作用域解析运算符来完成的。

14.4.8 将模板用作参数

模板可以包含类型参数(typename T)和非类型参数(如int n)。模板还可以包含本身就是模板的参数,这种参数是模板新增的特性, 用于实现STL。
stacktp头文件

#include 
#include 
#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); }
};
int main()
{
	using std::cout;
	using std::cin;
	using std::endl;
	Crab<Stack> nebula;
	int ni;
	double nb;
	cout << "Enter int double pairs, such as 4 3.5 (0 0 to end):\n";
	while (cin >> ni >> nb && ni > 0 && nb > 0)
		if (!nebula.push(ni, nb))
			break;
	while (nebula.pop(ni, nb))
		cout << ni << ", " << nb << endl;
	cout << "Done.\n";
	system("pause");
	return 0;
}
输出:
Enter int double pairs, such as 4 3.5 (0 0 to end):
50 22.48
25 33.87
60 19.12
0 0
60, 19.12
25, 33.87
50, 22.48
Done.

开头代码:

template <template <typename T> class Thing>
class Crab
//模板参数是template