网站主页   操作系统    网络工程    服务器    网页制作    数据库    程序开发    网络安全    办公软件   
讲座日期: 本周六下午1点30分 抢座
讲座地点: 北大青鸟马甸校区
主讲老师: 王老师 金牌讲师
讲座主题: 网络安全
讲座内容: 检测、防御、黑客信息,如何过滤不安全的网站,如何防御黑客的进攻。
订座电话: 010-82011432/33
  您当前位置:主页 > 网络学院 > 程序开发 > C#教程 >

模板和泛型如何配合使用




我将演示模板和泛型在何种情况下能配合使用,在何种情况下不能配合使用,并指明在 Visual C++® 2005 下当前模板实现方式的缺陷,以此来结束这一系列有关在 Microsoft® .NET Framework 中进行泛型编程的专栏。我选择通过讨论将标准模板库 (STL) 引入 .NET 的过程中进行的工作,来介绍这份材料。首先,我将回顾 STL 的基本元素,这样所有人都将站在同一起跑线上。

STL 的 CAI

有三个主要元素可用于标准模板库的设计:容器、算法和迭代器 (CAI)。STL 矢量和 List 类表示顺序容器。顺序容器保存第一个元素、第二个元素等等,直到最后一个元素。用程序来表示的函数参数列表通常作为包含字符串类型的元素的矢量来实现。例如:

vector paramlist; 

Map 和 Set 类表示关联容器。关联容器支持快速查找。例如,Map 表示键/值对:键用于查找,而值表示存储和检索的数据。要表示电话号码簿,您需要声明一个带有字符串键和整数值的 Map:

map phonedir;

多重映射可使单一键支持多个电话条目。

STL 还提供一个算法集,其中包含用于查找、排序、替换和合并的算法(可以对容器进行运算)。这些算法称为泛型算法,因为它们独立于正在其上进行运算的元素类型(例如整型、双精度类型或字符串类型)和包含元素的容器类型(例如无论容器是矢量、列表还是内置数组)。

泛型算法通过直接在容器上进行运算来实现容器独立性。程序不向算法传递容器,而是向它们传递标记了要通过其进行迭代的元素范围的迭代器对 (第一个, 最后一个],其中最后一个元素充当终结标志或一个标记,以表明元素集之后的元素并将停止算法:

sort( paramlist.begin(), paramlist.end() );

此处,begin() 和 end() 是所有 STL 容器提供的方法,会将迭代器返回到元素集第一个元素和位于末尾元素之后的元素。例如,看一下以下声明序列:

void f()
{
    int ia[4] = {21, 8, 5, 13 };
    vector ivec( ia, ia+4 );  //将 ivec 初始化为 ia...(请注意:示例程序文件中的程序员注释使用的是英文,本文中将其译为中文是为了便于参考)
    list   ilist( ia, ia+4);  //将 ilist 初始化为 ia ...

         // ...
}

注意:ia+4 实际指向最后一个元素 (13) 之后的地址。使用 STL 的人们最初可能被这种区别所蒙蔽,例如,传递截止到 ia+3 处,这将使元素 13 不能包括在一系列值中。

迭代器提供了一种统一且独立于类型的方式来访问、遍历和比较容器的元素。迭代器是一种类抽象,可以提供统一的指针操作(例如 ++、--、*、==、!=),无论容器是内置数组、列表还是其他任何一致的 STL 容器:

void f()
{
// ... 同上...

// 每次调用相同的泛型算法...
sort( ia, ia+4 );                       
sort( ivec.begin(), ivec.end() );       
sort( ilist.begin(), ilist.end() );        
}

在每个排序调用中(共三个),结果序列理所当然是:5、8、13、21(Fibonacci 序列的第四个到第七个元素)。

现在,这只是一种对 STL 的理想化观点,并不能证明在正式约束和实际约束下实际可行。正式约束是指:不是所有的容器类型均支持所有算法运算。例如,Map 或 Set 不支持 random_shuffle,因为对元素进行的任何重新排序均违反容器类型,这就像将索引编入堆栈中将违反堆栈的语义特征一样。

更实际地说,通过泛型算法,使用排序或查找来获得 List 容器中的元素,比在矢量上进行同一运算更加费力。因此,List 容器类型提供了自己的比泛型算法更高效的类方法。同样,使用 List 容器类型的查找方法来查找 Map 元素,比使用查找算法(通过向算法传递 Map 的开始和结束迭代器及其键)更快捷。

大家可能会希望算法在所有容器上的执行效果都相同,但实际上,算法更适合在 Block 容器和内置数组,而不是在 List 容器和关联容器类型上使用。实际上,当我在 Bell 实验室与 Alex Stepanov 一起工作时,他就把泛型算法称为 Block 算法。

为 .NET 重新设计 STL

要将 STL 合并到 .NET 中,首先要将容器作为公共语言运行库 (CLR) 类型重新实现。出于多种原因,我在本文中不会进行深入的讨论。总之,最好使容器成为引用类而不是值类。例如:

// 暂时简化声明...
template 
ref class vector { ... };

template 
ref class map { ... };

在本机 STL 中,所有容器都是非多态的。矢量的声明将给定实际矢量对象。示例如下:

// 本机 STL 矢量
//       ivec.empty() == true 
//       ivec.size() == 0
vector< int > ivec; 

//      ivec2.empty() == false 
//      ivec2.size() == 10
vector< int > ivec2( 10 );  

但是在 C++/CLI 下声明引用类型时,将定义一个跟踪句柄(矢量本身位于托管的堆中)。默认情况下,句柄将设置为 nullptr。请看:

public ref class sequence {
protected:
    vector ^elems;
};

public ref class fibonacci : sequence { ... };

    // equivalent test
    if ( elems == nullptr ) ...
    if ( ! elems ) ...
}

// ivec == nullptr;
vector< int >^ ivec;

// ivec2 != nullptr ...
// ivec2->empty() == true
// ivec2->size == 0
vector< int > ^ivec2 = gcnew vector; 

// ivec3 != nullptr ...
// ivec3->empty() != true
// ivec3->size() == 10 ...
vector< int > ^ivec3 = gcnew vector( 10 );

下一个设计要求是:使不支持模板的其他语言(例如 C# 和 Visual Basic®)能够使用容器。最简单的策略是:使模板容器实现一个或多个系统容器接口(分到两个命名空间中),如下所示:

System::Collections:: System::Collections::Generic:: ICollection IList IDictionary

通常,您将希望同时支持收集和泛型接口,以使当前使用收集接口的客户端能够使用您的类型。以下是声明支持两种接口的方法:
template 
ref class vector : 
System::Collections::ICollection,
System::Collections::Generic::ICollection
{ ... };

要实现系统收集命名空间的容器接口,还必须实现 IEnumerator 和 IEnumerator 的实例:

generic 
ref class vector_enumerator : 
System::Collections::IEnumerator,
System::Collections::Generic::IEnumerator
{ ... };

实现系统容器接口的弊端是:虽然使得元素可用,但是无法操作 STL/CLR 类型的容器。因此,额外的设计支持还应该提供泛型阴影容器类型,以使其他语言可以使用实际的容器类型。有两个常规策略可以实现这种支持:Plauger 方式和 Tsao 方式(以两个主要设计师 P. J. Plauger 和 Anson Tsao 的姓名来命名)。

可以认为 Plauger 方式提供泛型阴影类型。也就是说,您将创建阴影泛型类(可以将其称为 generic_vector)。它包含矢量模板的副本。示例如下:

generic 
public ref class vector_generic 
{
vector^ m_templ;    // 哎呀...

public:
vector_generic( vector^ );
};

m_templ 声明行上的“哎呀”注释表示在 .NET 下对模板使用的约束。由于存在这种约束,您不能以泛型类型存储要求实例化的模板。这是因为,两个参数化类型功能的实例化时间不同。泛型由运行时来实例化;而模板由编译器来实例化。因此,模板可以包含泛型,而泛型不能包含模板。

Plauger 方式下的解决方案为:创建一个公共泛型接口,模板和泛型均通过该接口派生。有关示例,请看:

generic 
interface class vector_interface {...};

template 
ref class vector : vector_interface{...}; 

generic 
public ref class vector_generic : vector_interface 
{
     vector_interface^ m_templ; 

public:
     vector_generic( vector_interface^ );
     // ...
};

Tsao 方式下的解决方案是根据以下事实得出的:接口始终为模板容器(在特定程序集中实例化)的引用。因此,您只需提供一个接口并实现模板即可。泛型阴影类型将被消除。

generic  
interface class vector_interface :ICollection {...};

template  
ref class vector :vector_interface, ICollection {...};  

在任何情况下,除了那些使用程序集的人们以外,所有人都可以执行泛型实例而不是 STL/CLR 容器。这是因为,C++/CLR 下的模板不能成为公共程序集成员。下一部分中讨论了此问题。

模板为非公共程序集类型。

要使 .NET 识别类型,它需要两个元素:程序代码(表示要转换为公共中间语言 (CIL) 的类型)和元数据(描述类型的详细信息)。不幸的是,模板此时还不能通过任何一个元素提供给 .NET。与析构函数一样,模板并不是为 .NET 而存在的。

.NET 仅能识别模板的具体实例;而不能识别出它们是一种模板。例如,.NET 可以识别 vector 和 vector 的 CIL 和元数据,但是不能识别共享的模板矢量,CIL 和元数据都是该模板矢量的实例。这种不可识别性的副作用是,您不能辨别模板。也就是说,您不能询问 vector 实例:“您是模板吗?如果是,请把您的参数列表和类定义传递给我好吗?”

另一方面,泛型在 CLR 2.0 中直接支持 CIL,并且具有完全支持泛型反射的扩展反射命名空间。这是您在选择设计方式(在应用程序中使用模板还是泛型类型)时,应该考虑的一个方面。还应考虑是否需要跨程序集共享。模板不能跨程序集共享,而泛型可以。因此,如果跨程序集共享对您的设计非常重要,则应选择泛型类而不是模板类。否则,您将需要提供某种形式的公共接口,来实现跨程序集的共享,正如我所介绍的有关 STL/CLR 设计的内容。

模板不能跨程序集识别的原因是:.NET 具有一个包含其源的扩展类型概念。也就是说,在 .NET 下与在本机 C++ 中不同,类型具有一个位置,在此位置名称可在共享的全局空间中自由浮动。这种全局名称混乱使得将各种组件合并到一个工作应用程序中非常困难。进一步的区别在于:命名空间将提供程序级别的解决方案,而为类型添加位置将提供程序集级别的解决方案。

也就是说,全局公共名称实际上由其程序集分配,从而避免在合并程序集时发生名称冲突。在 .NET 下,类型具有一个位置。这意味着,一个程序集的 vector 不会被识别为另一个程序集的相同 vector,因为类型已由单独的程序集名称标记。由于 CLR 提供的运行时实例化,泛型不会出现这种问题。

那么,既然存在这些约束,为什么我们还选择同时提供模板和 STL/CLR 呢?执行工作的 C++ 程序员已建立起该库和现有代码体的专业知识。我们不仅希望提供现有代码的迁移路径,还希望提供现有专业知识的迁移路径。如果您以前在 C++ 编程过程中依靠 STL,则会感到在 .NET 下缺少 STL 是一种损失。而使用 STL/CLR 则不会这样。我后来听说,计划在 STL 完成后使其可以下载使用。请继续关注!我知道我会的。

请将您的疑问和意见通过  发送给 Stanley。

Stanley B. Lippman 从 1984 年开始在 Bell 实验室与 C++ 的发明者 Bjarne Stroustrup 一起使用 C++。后来,Stan 在 Disney 和 DreamWorks 的动画部门工作过,还担任过 Fantasia 2000 的软件技术主管。此后他一直担任 JPL 的杰出顾问,以及与 Microsoft 公司 Visual C++ 团队合作的设计师。

 

 

上一篇:C#中调用Windows API的要点  
下一篇:嵌入式系统中USB驱动程序设计
相关信息:


Copyright © 2004-2015 北大青鸟马甸校区 北京北方华腾技术培训中心 版权所有
学校地址:北三环中路马甸桥东北角商房大厦(国美电器)626
招生热线:010-82011433/32 京公网安备110102004704  京ICP备05043413号 京公网安备110102004704