科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Visual Studio 2005体验泛型编程

Visual Studio 2005体验泛型编程

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

     Visual Studio 2005 为 Microsoft .NET 框架带来了泛型编程的类型参数化模型。当然,类型参数化是C++程序员的事情。所以,对于那些还不熟悉它们的人,我将在本文中对泛型编程做一个简要的介绍。

作者:中国IT实验室 来源:中国IT实验室 2007年9月29日

关键字: 编程

  • 评论
  • 分享微博
  • 分享邮件
  
  Visual Studio 2005 为 Microsoft .NET 框架带来了泛型编程的类型参数化模型。当然,类型参数化是C++程序员的事情。所以,对于那些还不熟悉它们的人,我将在本文中对泛型编程做一个简要的介绍。
  
  泛型编程的基本思想是交付固定的代码库,这个代码库支持潜在的无限类型集合。有两种用于泛型编程的常规模型:通用类型容器模型(Universal Type Container Model,UTCM)和类型参数化模型(Type Parameter Model,TPM)。
  
  在 UTCM 中,与对象相关的类型信息已经被剥离。因为它是可还原的,所以容易实现。所有的对象都以一种统一的、非透明的方式存储。而在一个像 CTS(Common Type System)这样单一的类型体系中,通用类型容器就是即 Object;所有的 CTS 类型均直接或间接地的从 Object 中派生。比如说 C 语言中,void * 就是 通用类型。
  
  在 TPM 中,与对象关联的类型信息的绑定已经被提炼和延迟。从一个调用到另一种调用中,值的变化多种多样,它们被提炼为参数。这就是为什么这种实现模型被称为参数化 类型的原因――它更复杂,但功能强大。
  
  例如,你在实现一个 System::Collections 名字空间的 IEnumerator 接口。只要提供两个方法和一个属性,好像很简单。但是在强类型语言中,提供对所有用户都可用的单一接口其难度是无法想象的。其难就难在我们的实现中 无法再使用“???”;
  
  interface class IEnumerator
  {
  property ??? Current { ??? get(); }
  bool MoveNext();
  void Reset();
  };
  
  类型系统需要你静态标识与属性的存储器回填相关的类型以及获取存取器(accessor)返回类型,但这当然是不可能的。用户需要枚举的潜在类型无以计数。你怎么办?
  
  简单一点的常规解决办法是 UTCM,在这里对象被作为容器。
  
  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<String^>( o );
  
  在实现集合时碰到的问题更多,因为无法静态约束某个集合在通用类型容器模型下仅容纳单一类型的对象。这只能从程序一级提供,而且稍显复杂和易错。此外,因为它是一个程序解决方案,只能被用于运行时期。 你再次得不到编译器的支持。
  
  除了安全性和复杂性之外,还涉及大规模存储以及在通用类型容器模型下获取值类型的性能问题。借助类型参数化,这三个问题迎刃而解。
  
  什么是参数化的类型?
  
  类型参数模型提供了第二层隔离,消除了向下强制类型转换和框入/框出操作,并允许编译时同类容器元素类型冲突的降格(flagging)。它是一种两步解决方案。在第一步中,将一个类型参数当作一个 实际类型的占位符,就像函数所做的那样:
  
  interface class IEnumerator
  {
  property typeParameter Current { typeParameter get(); }
  bool MoveNext();
  void Reset();
  }
  
  在第二步,你告诉编译器(程序的机器阅读器),typeParameter 是一个占位而不是一个程序实体。这一步是通过叫做参数化列表的泛型署名来完成的。在C++/CLI中, 要么引入 generic 关键字以选择公共语言运行时(CLR)泛型机制,要么引入 template 关键字以选择使用 C++ 模板类型机制。如 Figure 1 所示。
  
  Figure 1 Indicating typeParameter
  Generic Keyword
  generic <typename typeParameter>
  interface class IEnumerator
  {
  property typeParameter Current { typeParameter get(); }
  ...
  };
  
  Template Keyword
  template <typename typeParameter>
  interface class IEnumerator
  {
  property typeParameter Current { typeParameter get(); }
  ...
  };
  
  这样便导致了一个类型无关的接口定义。之后,当某个类实现 IEnumerator 时,它必需提供一个实际的类型绑定到 typeParameter 占位 符。这是通过将括弧中实际的类型与参数化类名配对实现的,比如IEnumerator<int>
  
  C++/CLI 支持两种参数化类型机制,模板和泛型,用于定义参数化引用、值和接口类,函数和委托。从表面上看,参数化的 generic 和 template 至少在语句构成上是等同的(除了 template 或 generic 关键字有所不同)。而在其它方面,它们有显著的不同。
  
  考虑一下 Figure 2 中的两个栈声明,template 实例(tStack),通过标准模板库(STL)的 CLI 实现提供了一个使用动态 vector 容器的例子,以及 generic 实例(gStack),通过 System::Collections::Generic 名字空间提供的使用动态 List<T> 容器的例子。两者在 Visual Studio 2005 中都是新的参数化类型集合库。
  
  Figure 2 Template and Generic Stack Declaration
  C++/CLI Template Stack Declaration
  #include <cliext/vector>
  using namespace cliext;
  
  template <class elemType>
  ref class tStack
  {
  // the CLI vector is a reference class …
  vector<elemType> ^m_stack;
  int top;
  
  public:
  tStack();
  elemType pop();
  void push( elemType et );
  ...
  };
  
  C++/CLI Generic Stack Declaration
  using namespace System::Collections::Generic
  
  generic <class elemType>
  public ref class gStack {
  List<elemType> ^m_stack;
  int top;
  
  public:
  gStack();
  elemType pop();
  void push( elemType et );
  ...
  };
  
  通过在类名后的尖括弧中指定实际类型来创建参数化类型实例。例如,Figure 3 依次示范了用整型和字符串类型参数实例化的 template 堆栈。为了创建等同的 generic 堆栈实例,Figure 4 所用的两种类型参数是相同的。
  
  Figure 3 Instantiating the Template Stack
  void demo_template_Stack()
  {
  // an int value type argument...
  
  tStack<int>^ is = gcnew tStack<int>( 10 );
  for ( int ix = 0; ix < 10; ix++ )
  is->push( ix*2 );
  
  int elem_cnt = is->size();
  for ( int ix = 0; ix < elem_cnt; ++ix )
  Console::WriteLine( "({0}) {1}", ix+1, is->pop());
  
  // a String^ reference type argument...
  
  tStack<String^> ^ss = gcnew tStack<String^>( 10 );
  ss->push( "Pooh" ); ss->push( "Piglet" );
  ss->push( "Rabbit" ); ss->push( "Eeyore" );
  
  elem_cnt = ss->size();
  for ( int ix = 0; ix < elem_cnt; ++ix )
  Console::WriteLine( "({0}) {1}", ix+1, ss->pop());
  }
  
  Figure 4 Instantiating the Generic Stack
  
  void demo_generic_Stack()
  {
  // an int value type argument...
  
  gStack<int>^ is = gcnew gStack<int>( 10 );
  for ( int ix = 0; ix < 10; ix++ )
  is->push( ix*2 );
  
  int elem_cnt = is->size();
  for ( int ix = 0; ix < elem_cnt; ++ix )
  Console::WriteLine( "({0}) {1}", ix+1, is->pop());
  
  // a String^ reference type argument...
  
  gStack<String^> ^ss = gcnew gStack<String^>( 10 );
  ss->push( "Pooh" ); ss->push( "Piglet" );
  ss->push( "Rabbit" ); ss->push( "Eeyore" );
  
  elem_cnt = ss->size();
  for ( int ix = 0; ix < elem_cnt; ++ix )
  Console::WriteLine( "({0}) {1}", ix+1, ss->pop());
  }
  
  参数化类型对象的实际处理,例如 is 和 ss,与非参数化类型对象的处理完全一样。参数化类型的一个好处是单一的源定义能潜在地产生出无数种型实例。该例子中,generic 和 template 堆栈类在相同的参数化类源代码之外都支持字符串和整型类。在所有已知类型的应用程序中使用它们时没有真正的约束。正如你将会在后续专栏中看到的那样,并不是所有参数化类型都这样。
  
  generic 和 template 定义以及种型实例在这里几乎是等价的,尽管并不是所有的参数化类型都这样。或者说在支持两种机制的 C++/CLI 中益处不多。当我在后续专栏中详细讨论两种机制时,你会看到其它一些差异。正是存在这些差异,在我遇到它们时,将它突出出来,而不是反复说其共性,似乎是整合其全貌的更好方法。
  
  类型参数列表
  

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件