template的声明,也就是说当你声明一个template class、template class member function等时,会发生什么事情。
如何”实例化“class object、inline nonmember以及member template functions。这些是”每一个编译单位都会拥有一份实例“的东西。
如何”实例化“nonmember、member template functions以及static template class members。这些都是每一个可执行文件中只需要一份实例的东西。
template
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 *freeList;
static int chunkSize;
Type _x,_y,_z;
};
当编译器看到template class的时候不会有任何反应,也就是说static data member并不可用,嵌套enum也一样。
其中,enum Status的真正类型在所有的Point 实例中都一样。但我们依旧只能通过template Point class的某个实例来存取和操作。即我们可以这样写:
Point::Status s;//正确
Point::Status s;//错误
//同样freeLsit和chunkSize对程序而言也不可用
Point::freeList;//错误
Point::chunkSize;//错误
Point::freeList;//正确
Point::chunkSize;//正确
// case1:
Point< float> *ptr=0;
// 程序什么也不会发生,因为指向的是 class object的指针
// 本身并不是一个class object, 编译器不需要知道与该class
// 任何有关的任何member数据或者object数据布局
// case2: 若是引用
const Point &ref = 0;
//此时,编译器会实例化一个Point的float实例,它会被扩展为一下形式:
//内部扩展
Point temp(float(0));
//因为引用并不是无物的代名词,0被视为整数,必须被转化为一下类型的一个对象
const Point &ref = temp;
// case3
Point
//一个class object的定义,不论是由编译器暗中的做(像temp那样),或是由程序员显式的做
const Point origin;
case2 和 case3 都会导致template class 的实例化。
然而,member function(成员函数)(至少对那些未被使用过的)不应该被实例化,只有在被使用的时候,C++标准才要求它们被实例化。
由使用者来主导”实例化“规则主要有两个原因:
空间和时间效率的考虑。因为一个类可能会有很多member functions,但一个class实例并不一定会用到所有的member function。
尚未实现的机能。例如:origin的定义需要调用Point的默认构造函数和析构函数,因此只有这两个函数需要被实例化。
实例化这些函数:目前有两种策略:(1)在编译的时候;(2)在链接的时候。
template< class T >
class Mumble
{
public$: //第一处错误,非法标识符$
Mumble( T t=1024 ) //第二处错误,t被初始化1024,但如果我们给mumble绑定的是char呢?
:_t(t) //第三处错误,_t并不是哪一个mumble中的成员,tt才是。这种错误一般会在类型检查这个阶段查找出来。每一个名称必须绑定在一个定义身上,要不就会被判定错误!
{
if( tt != t ) //第四处错误, !=运算符有可能还没有定义好【视T的真正绑定类型来定】,和第二点一样,只有template的各个实体才能诊断出来。
throw ex ex; //第五处错误,非法标识符ex ex;这种错误会在编译时起的解析阶段被发现,c++语言中一个合法的句子不允许一个标识符后面紧跟一个标识符。
}
private:
T tt;
};
在一个non template class声明中,这五个错误会在编译时期就能查出来。但template class却不同,举个例子,所有与类型有关的检验,如果牵扯到template参数,都必须延迟到真正的具现【instantiation】操作时才发生,也就是说,第二处错误与第四处错误会在每个具现【instantiation】操作时才能被检查出来,其结果会因为不同的实际绑定类型而不同,比如:
Mumble m; //合法的
则第二处与第四处就不是错误,但如果是
Mumble pm; //非法的
那么第四处依然正确,但第二处肯定是错误!因为在c++中,不允许将一个除了0之外的整数常量赋值给指针。
那么,什么样的错误会在编译器处理template声明时被找出来?这里有一部分和template的处理策略有关。cfront对template的处理是完全解析【parse】,但不做类型检验,只有在每一个具现【instantiation】操作时才做类型检验,所以在这种parse策略之下,所有的语汇错误和解析错误都会在处理template声明时被找出来。
语汇分析器【lexical analyzer】会在第一处错误那里捕捉到一个非法标识符,解析器【parser】会这样标识它:( public$: // caught ),表示这是一个不合法的卷标【label】,但解析器不会把“对一个未命名的member成员做出参考操作”视为错误,所以第三处错误并不归解析器负责,但解析器仍然会抓住第五处错误。
在一个十分普遍的替代策略中,template声明被收集为一系列的“lexical tokens”,而parsing操作延迟到有真正具现【instantiation】操作时才开始:每当看到一个instantiation,相关的token就会被推往parser,然后调用类型检验查找有无错误等等;面对先前出现的那个template声明,“lexical tokenizing”会指出什么错误吗?事实上很少,只有第一处错误那里的非法标识符会被找出,其余的template声明都会被解析为合法的tokens并收集起来!
Nonmember和member template function在具现【instantiation】发生之前也没有做到完全的类型检验,这导致某些十分离谱的错误竟然可以编译通过,例如下面这个:
template
class Foo
{
public:
Foo();
type val();
void val(type v);
private:
type _val;
}; //以上这些没啥问题
...
template
double Foo::hello(){ return this->world; } //错误在这里!看里面的hello和world,我们可有在Foo里面声明?
不论是cfront还是Sun编译器亦或borland,都不会对上面的代码产生怨言!
再说一次,上面的策略,都是编译器设计者自己的决定,template facility并没有说不允许对template声明的类型部分有更严格的检查,当然,其实这样的错误可以在编译时起发现,只不过大家懒得这么做罢了。
1.scope of the template definition(定义出template的域)
2.scope of the template instantiation(实例化template的域)
//scope of the template definition
extern double foo( double );
template
class ScopeRules
{
public:
void invariant()
{
_member = foo(_val);
}
type type_dependent()
{
return foo(_member);
}
private:
int _val;
type _member;
};
//scope of template instantiation
extern int foo( int );
ScopeRules sr0;
此时如果有以下调用:
sr0.invariant();
//那么在invariant()中调用的究竟是哪个foo()函数实例?答案是
extern double foo( double );
因为,在 Template 中,对于一个nonmember name(非成员名称)的决议结果,是根据这个name的使用是否 与”用以实例化该template的参数类型“有关而决定的。
如果不相关,就使用scope of the template definition来决定name;
如果相关,就使用scope of template instantiation来决定name;
如果此时有如下调用:
sr0.type_dependent();
那么此时会调用scope of template instantiation中声明的foo()函数,而在该例子中,共有两个foo()函 数,且此例的_member类型为int,所以调用
extern int foo( int );
//如果是
ScopeRules< double > sr0;
//那么就会调用
extern double foo( double );
不管如何演变,都是由“scope of template instantiation”来决定。
总结如下:
scope of template definition//用以专注于一般的template class
scope of template instantiation//用以专注于特定的实例
template functions的实例化:
目前有两个策略,一个是编译时期策略,另一个是链接时期策略。
但这两个策略都有一个共同的缺点:当template实例被产生出来时,有时候会大量增加编译时间。
总结
因为模板类型不确定,所以对一个模板类型的变量赋初值可能会是错误的。因为模板类型不确定,所以并不是所有运算符都会支持。模板最后应该以分号结束。因为,在模板类中,所有关于类型的检查会延迟到实例化之后才会发生。
一个编译器要保持两个scope contexts,其实也就是模板一般化和模板特化,一个用以一般的模板类,另一个用以专注于特定的实例。
深度探索c++对象模型之template的错误报告_c++ 引入 template 错误-CSDN博客