类型参数列表 每种类型参数都是以 class 或 typename 关键字开始的。这些关键字并包含任何平台意义——例如,class 并不是暗示要是一个本地类型,typename 也不是 意味着就是公共语言基础结构(CLI)类型。它们都表示紧跟着的名字是一个参数化类型的占位符,该占位符将会被用户指定的类型参数所取代。
之所以用中两个关键字是有历史原因的。在最初的模板规范中,Stroustrup 重用了现有的 class 关键字来指定一个类型参数而不是引入可能破坏已有程序的新关键字。直到 ISO-C++ 标准,class 关键字是声明类型参数的唯一方法。
重用现有的关键字似乎总是容易产生混淆。使用 class 来表示类型参数(parameter)是不是比内建类型和指针类型更能限制可用类型参数(arguments)成为 class 类型呢?不是,那么在这种情况下使用 class 就不会使人误解吗?肯定会的。所以,有些人觉得不引入新的关键字会导致不必要的混乱。但是那不是引入 typename 关键字的原因。
事实上,将 typename 引入 C++ 的真正的原因是为了支持模板定义的解析。这是个比较深入的话题,我在此只做一点简要介绍。详细描述请参考 Stroustrup 的 《Design and Evolution of C++》(Addison-Wesley, 1994)。
在某些情况下,要编译器来区分类型声明和表达式是不可能的。如果编译器遇到某个模板定义中的表达式Parm::name,并且 Parm 是一个表示 class 的模板类型参数,那么名称是该叫类型成员还是 Parm 的数据成员呢?
template <class Parm, class U> Parm minus( Parm* array, U value ) { Parm::name * p; // Is this a pointer declaration or // a multiplication expression? // By default treated as expression. } |
默认情况下,这个表示方法被认为是一个乘法表达式:运算符 Parm::name 乘以 p。关键字 typename 的引入使程序员能重写这种默认的解释。例如, 为了声明 Parm::name 类型的指针 p,可将模板函数重写如下:
template <class Parm, class U> Parm minus( Parm* array, U value ) { typename Parm::name * p; // ok: pointer declaration } |
既然这个关键字的存在已经是一种既定的事实,那么非要消除因重用 class 关键字而导致的混乱是很不明智的作法。公布的代码、书籍、文章、言论、论坛和出版物 都广泛使用它,因此不能对之视而不见。这就是为什么 C++ 对这两个关键字都支持的原因。
关键字 class 或 typename 的后面是一个标识符,它在 template 或 generic 定义充当占位符。在参数列表中的每一个标识符必须唯一。但是,在 交叉声明中这两个关键字和标识符是可以改变的:
template <class T> public ref class tStack; // ok: both the keyword and identifier can vary across // declarations of the same type template <typename elemType> public ref class tStack {}; |
标识符的作用域用于持续类型声明的范围。在 tStack 的前向声明中,用分号结束,并且这个名字从没有被引用过。在实际定义中, 不论是在类定义中,还是在该类的每个以非内联(out-of-line)方式定义的成员函数中,这个标识符都是可见的。
类型实例化 template 或 generic 定义指明了当给定一个或多个实际类型集合时,如何构造单一的类或函数。实例化的时机是模板和泛型之间的一个主要区别之一。Template 的实例化是在编译时完成的;而 generic 的实例化是 CLR 在运行时完成的(在后面 专栏中,我会作更详细的介绍)。
template 定义做为一个自动产生特定类型实例的图解;编译器从字面上插入由用户提供的特定类型参数。而 generic 定义则更像是个蓝图;在运行时 构造特定类型实例,根据类型参数是引用还是值类型来修改常规语法。例如,用如下的代码,你可以从 template 和 generic 定义自动创建一个 int 类型的堆栈类对象和一个 String 类型的堆栈类对象: tStack<int> ^si;
tStack<String^> ^ss;
这个从模板定义中产生的类被称为模板实例化——在 ISO-C++ 标准中就是这样讨论的。在泛型的文字描述中,类的生成被称为构造——这 里又看到了模板与蓝图之间的不同之处。这里,我用“实例化”来描述这一过程。当 String 类型的堆栈类被实例化时,在 generic 或 template 定义中每每出现模板参数的地方都用 String 类型取代。该类型的正确性被验证。
实例化的名称是 Stack<int> 或 Stack<String^>。紧随名字后面的 <int> 或 <String^> 符号在ISO-C++中被称为模板参数。而在 generic 表述中 则称为类型参数,本文我将遵循这样的叫法。类型参数必须在用逗号分隔的列表中指定,并用尖括号括起来。实例化的名称必须要显式指定参数类型。与函数实例化类型参数不同,用于类实例化的类型参数决不能从所使用的类实例上下文来推断——其含义将在未来关于参数化函数和函数类型的专栏中讨论。
某个类的实例化可以在常规程序中任何使用非参数化类类型的地方使用,同样,某个实例化后的类对象的声明和使用与非参数化类完全相同。
最后,派生类和基类——绑定到独立类型参数的两个 generic(或 template)类型实例之间是没有特别关系的。认识这一点很重要。比如说,你不能在没有显式编程操作的情况下,初始化或将一个赋值给另一个。即便对整型实例化对象的非公有成员具有存取许可,你也不能进行堆栈 String 实例化操作。
查看本文来源