分类: C/C++
2008-08-04 09:35:06
interface class IEnumerator { property ??? Current { ??? get(); } bool MoveNext(); void Reset(); };类型系统需要你静态标识与属性的存储器回填相关的类型以及获取存取器(accessor)返回类型,但这当然是不可能的。用户需要枚举的潜在类型无以计数。你怎么办?
interface class IEnumerator { property Object^ Current { Object^ get(); } bool MoveNext(); void Reset(); };这样提供了一定程度上的隔离。它允许用单一不变的代码库来支持潜在的无穷多的类型。并且对于被动存储和引用类型对象的获取,其工作表现不俗。
extern void f( Object^ anyTypeWorks ); Object^ o = "a string of all things"; // no downcast ... passive storage f( o ); // downcast ... we need to manipulate String^ s = safe_cast在实现集合时碰到的问题更多,因为无法静态约束某个集合在通用类型容器模型下仅容纳单一类型的对象。这只能从程序一级提供,而且稍显复杂和易错。此外,因为它是一个程序解决方案,只能被用于运行时期。 你再次得不到编译器的支持。( o );
interface class IEnumerator { property typeParameter Current { typeParameter get(); } bool MoveNext(); void Reset(); }在第二步,你告诉编译器(程序的机器阅读器),typeParameter 是一个占位而不是一个程序实体。这一步是通过叫做参数化列表的泛型署名来完成的。在C /CLI中, 要么引入 generic 关键字以选择公共语言运行时(CLR)泛型机制,要么引入 template 关键字以选择使用 C 模板类型机制,如 Figure 1 所示。
IEnumerator
C /CLI
支持两种参数化类型机制,模板和泛型,用于定义参数化引用、值和接口类,函数和委托。从表面上看,参数化的 generic 和
template 至少在语句构成上是等同的(除了 template 或 generic 关键字有所不同)。而在其它方面,它们有显著的不同。
考虑一下
Figure 2 中的两个栈声明,template 实例(tStack),通过标准模板库(STL)的 CLI 实现提供了一个使用动态
vector 容器的例子,以及 generic 实例(gStack),通过 System::Collections::Generic
名字空间提供的使用动态 List
通过在类名后的尖括弧中指定实际类型来创建参数化类型实例。例如,Figure 3 依次示范了用整型和字符串类型参数实例化的 template
堆栈。为了创建等同的 generic 堆栈实例,Figure 4 所用的两种类型参数是相同的。
参数化类型对象的实际处理,例如 is 和 ss,与非参数化类型对象的处理完全一样。参数化类型的一个好处是单一的源定义能潜在地产生出无数种型实例。该例子中,generic
和 template
堆栈类在相同的参数化类源代码之外都支持字符串和整型类。在所有已知类型的应用程序中使用它们时没有真正的约束。正如你将会在后续专栏中看到的那样,并不是所有参数化类型都这样。
generic 和 template 定义以及种型实例在这里几乎是等价的,尽管并不是所有的参数化类型都这样。或者说在支持两种机制的 C /CLI
中益处不多。当我在后续专栏中详细讨论两种机制时,你会看到其它一些差异。正是存在这些差异,在我遇到它们时,将它突出出来,而不是反复说其共性,似乎是整合其全貌的更好方法。
类型参数列表
每种类型参数都是以 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默认情况下,这个表示方法被认为是一个乘法表达式:运算符 Parm::name 乘以 p。关键字 typename 的引入使程序员能重写这种默认的解释。例如, 为了声明 Parm::name 类型的指针 p,可将模板函数重写如下:Parm minus( Parm* array, U value ) { Parm::name * p; // Is this a pointer declaration or // a multiplication expression? // By default treated as expression. }
template既然这个关键字的存在已经是一种既定的事实,那么非要消除因重用 class 关键字而导致的混乱是很不明智的作法。公布的代码、书籍、文章、言论、论坛和出版物 都广泛使用它,因此不能对之视而不见。这就是为什么 C 对这两个关键字都支持的原因。Parm minus( Parm* array, U value ) { typename Parm::name * p; // ok: pointer declaration }
template标识符的作用域用于持续类型声明的范围。在 tStack 的前向声明中,用分号结束,并且这个名字从没有被引用过。在实际定义中, 不论是在类定义中,还是在该类的每个以非内联(out-of-line)方式定义的成员函数中,这个标识符都是可见的。public ref class tStack; // ok: both the keyword and identifier can vary across // declarations of the same type template public ref class tStack {};
tStack这个从模板定义中产生的类被称为模板实例化——在 ISO-C 标准中就是这样讨论的。在泛型的文字描述中,类的生成被称为构造——这 里又看到了模板与蓝图之间的不同之处。这里,我用“实例化”来描述这一过程。当 String 类型的堆栈类被实例化时,在 generic 或 template 定义中每每出现模板参数的地方都用 String 类型取代。该类型的正确性被验证。^si; tStack ^ss;