19、Template

发布于:2025-03-16 ⋅ 阅读:(12) ⋅ 点赞:(0)

template是最令程序员挫败的一个主题,这一节的聚焦放在template的语意上面,我们将讨论templates在编译系统中“何时”、“为什么”以及“如何”发挥其功能。下面是有关template的三个主要讨论方向:

  1. template的声明。基本来说是当你声明一个template class、template class member function等等时,会发生什么事情。
  2. 如何“实例化”class object、inline nonmember以及member template functions。这些是“每一个编译单位都会拥有的一份实例”的东西。
  3. 如何“实例化”nonmember、member template functions以及static templates class members。这些都是“每一个可以执行文件中只需要一份实例”的东西。

我们使用“实例化”来表示进程(process)将真正的类型和表达式绑定到template相关形式参数(formal parameters)上头。

举个例子。

template <class Type>
Type
min(const Type &t1, const Type &t2);

用法如下:

min( 1.0, 2.0);

于是进程就把Type绑定为double并产生min()的一个程序文字实例(并施加以“mangling”手术,给它一个独一无二的名字),其中t1和t2的类型都是double。

Template的“实例化”行为

考虑下面的template Point class:

template<class Type>
class Point
{
public:
	enum Status{unallocated, normalized};
	
	Point(Type x = 0.0, Type y = 0.0,Type z=0.0);
	~Point();
	
	void *operator new(size_t);
	void operator delete(void*, size_t);
	
	//...
private:
	static Point<Type> *freeList;
	static int chunkSize;
	Type _x, _y, _z;
};

首先,当编译器看到template class声明时,它会做出什么反应?在实际的程序中,什么反应也没有。也就是说,上述的static data members并不可用。

虽然enum Status的真正类型在所有的Point instantiations中都一样,其enumetators也是,但是他们每一个都只能够通过template Point class的某个实例来存取。因此我们可以这样写:

// ok
Point<float>::Status s;

但是不能这样写:

// error
Point::Status s;

即使两种类型抽象也是一样的。同样的道理,freeList和chuckSize对程序而言也还不可用,我们不能够写:

// error 
Point::freeList

我们必须显示地指定类型,才能够使用freeList:

//ok
Point<float>::freeList

像上面这样使用static member,会使其一份实例与Point class的float instantiation在程序中产生关联。如果我们写:

//ok:另一个实例
Point<double>::freeList

就会出现第二个freeList实例,与Point class的double instantiation产生关联。

如果我们定义一个指针,指定特定的实例,像这样子:

Point<float> *ptr = 0;

再一次,程序什么都没有发生。为什么?因为一个指向class object的指针,本身并不是一个class object,编译器不需要知道与该class有关的任何members的数据或object布局数据。

如果不是pointer而是reference,又如何?假设:

const Point<float> &ref = 0;

是的,它真的会实例化一个“Point的float实例”。这个定义的真正语意会被扩展为:

//内部扩展
Point<float> temporary( float(0) );
const Point<float> &ref = temporary;

为什么?因为reference并不是无物(no object)的代名词。0被视为整数,必须被转换成以下类型的一个对象:

Point<float>

如果没有转换的可能,这个定义就是错误的,会在编译时被挑出来。

所以,一个class object的定义都会导致template class 的“实例化”,也就是说,float instantiation的真正对象布局会被产生出来。回顾先前的template声明,我们看到Point有三个nonstatic members,每一个的类型都是Type。Type现在被绑定为float,所以origin的配置空间必须足够容纳三个float成员。

这些函数在什么时候“实例化”?目前流行两种策略:

在编译的时候。那么函数将“实例化”于origin和p存在的那个文件中。

在链接的时候。那么编译器会被一些辅助工具重新激活。Template函数实例可能被放在这一个文件中、别的文件中或者一个分离的存储位置。

Template的错误报告(Error Reporting within a Template)

考虑下面的template声明:

(1)template <class T>

(2)class Mumble

(3){

(4)public:

(5) Mumble(T t=1024)

(6) :_t(t)

(7) {

(8)  if(tt != t)

(9)   throw ex ex;

(10) }

(11)private:

(12) T tt;

(13)};

这个Mumble template class的声明内含有一些错误:

L5:t被初始化为整数常量1024,或许可以。视T的真实类型而定,只有template的各个实例才能诊断出来。

L6:_t并不是哪一个member的名称,tt才是。这种错误一般会在“类型检验”这个阶段被找出来。

L8: !=运算符可能已经定义好了,也可能还没有定义,视T的真正类型而定,和L5相同。

L9:我们意外的键入ex两次。

L5和L8的潜在错误会在每个实例化操作(instantiation)发生时被检查出来并记录之。

cfront对template的处理是完全解析(parse)但不做类型检查;只有在每一个实例化发生时才做类型检查。

Template中名称决议法

一种是C++ Standard所谓的“scope of the template definition”,也就是“定义出template”的程序端。另一种是C++ Standard所谓的“scope of the template instantiation”,也就是“实例化”的程序端。

第一种情况如下:

//scope of the template definition
extern double foo(double);

template <class type>
class ScopeRules
{
public:	
	void invariant(){
		_member = foo( _val );
	}
	type type_dependent()
	{
		return foo(_member);
	}
	//...
private:
	int _val;
	type _member;
};

第二种情况如下:

//scope of the template instantiation
extern int foo(int);
//...
ScopeRules<int> sr0;

在ScopeRules template中有两个foo()调用操作。在“scope of the template definition”中,只有一个foo()函数声明在scope之内。然而在“scope of the template instantiation”中,两个foo()函数声明都位于scope之内。

//scope of the template instantiation

sr0.invariant();

那么,在invariant()中调用究竟是哪一个foo()函数实例呢?

_member = foo(_val);

在调用foo()上,这里有俩个函数实例:

//scope of the template definition
extern double foo(double);

//scope of the template instantiation
extern int foo(int);

在Template之中,对nonmember name的决议结果,是根据这个name的使用是否与“实例化该template的参数类型”有关决定的。如果其使用互不关联,那么就以“scope of the template definition”来决定name。如果其使用相互关联,那么就以“scope of the template instantiation”来决定name。

在_member = foo(_val);中,_val与该template的参数类型无关,它是int固定类型。

//the resolution of foo() is not 
//dependent on the template argument
_member = foo( _val );

因此选择

//scope of the template definition
extern double foo(double);

让我们看看“与类型相关”(type-dependent)的用法:

sr0.type_dependent();

这个函数的内容如下:

return foo(_member);

它会调用哪一个foo()呢?

这个例子中,很清楚的看到与template参数有关,所以这一次foo()必须在“scope of the template instantiation”中决议。由于_member实例化的类型是int,因此int版本的foo()出线。

Member Function的实例化行为

对于template的支持,最困难的莫过于template function的实例化(instantiation)。目前编译器提供两种策略:一种是编译时期策略,程序代码必须在program text file中准备妥了。另一种是链接时期策略,有一些meta-compilation工具可以引导编译器的实例化行为。


网站公告

今日签到

点亮在社区的每一天
去签到