C++/CLR泛型与C++模板之间的比拟
当前位置:以往代写 > C/C++ 教程 >C++/CLR泛型与C++模板之间的比拟
2019-06-13

C++/CLR泛型与C++模板之间的比拟

C++/CLR泛型与C++模板之间的比拟

副标题#e#Visual Studio 2005把泛型编程的范例参数模子引入了微软.NET框架组件。C++/CLI支持两种范例参数机制–通用语言运行时(CLR)泛型和C++模板。本文将先容两者之间的一些区别–出格是参数列表和范例约束模子之间的区别。

  参数列表又返来了

  参数列表与函数的信号(signature)雷同:它标明白参数的数量和每个参数的范例,并把给每个参数关联一个独一的标识符,这样在模板界说的内部,每个参数就可以被独一地引用。

  参数在模板或泛型的界说中起占位符(placeholder)的浸染。用户通过提供绑定到参数的实际值来成立工具实例。参数化范例的实例化并非简朴的文本替代(宏扩展机制就是利用文本替代的)。相反地,它把实际的用户值绑定到界说中的相关的形式参数上。

  在泛型中,每个参数都表示为Object范例或衍生自Object的范例。在本文后头你可以看到,这约束了你大概执行的操纵范例或通过范例参数声明的工具。你可以通过提供越发明晰的约束来调解这些约束干系。这些明晰的约束引用那些衍生出实际范例参数的基类或接口荟萃。

  模板除了支持范例参数之外,还支持表达式和模板参数。另外,模板还支持默认的参数值。这些都是凭据位置而不是名称来解析的。在两种机制之下,范例参数都是与类或范例名称要害字一起引入的。

  参数列表的特另外模板成果

  模板作为范例参数的增补,答允两种范例的参数:非范例(non-type)参数和模板参数。我们将别离简短地先容一下。

  非范例参数受常数表达式的约束。我们应应当即想到它是数值型或字符串常量。譬喻,假如选择提供牢靠巨细的仓库,你就大概同时指定一个非范例的巨细参数和元素范例参数,这样就可以同时凭据元素种别和巨细来分别仓库实例的种别。譬喻,你可以在代码1中看到带有非范例参数的牢靠巨细的仓库。

  代码1:带有非范例牢靠巨细的仓库

template <class elemType, int size>
public ref class tStack
{
 array<elemType> ^m_stack;
 int top;

 public:
  tStack() : top( 0 )
  { m_stack = gcnew array<elemType>( size ); }
};

  另外,假如模板类设计者可觉得每个参数指定默认值,利用起来就大概利便多了。譬喻,把缓冲区的默认巨细配置为1KB就是很好的。在模板机制下,可以给参数提供默认值,如下所示:

// 带有默认值的模板声明
template <class elemType, int size = 1024>
public ref class FixedSizeStack {};
  用户可以通过提供明晰的第二个值来重载默认巨细值:

// 最多128个字符串实例的仓库
FixedSizeState<String^, 128> ^tbs = gcnew FixedSizeStack<String^, 128>;
  不然,由于没有提供第二个参数,它利用了相关的默认值,如下所示:

// 最多1024个字符串实例的仓库
FixedSizeStack<String^> ^tbs = gcnew FixedSizeStack<String^>;
  利用默认的参数值是尺度模板库(STL)的一个根基的设计特征。譬喻,下面的声明就来自ISO-C++尺度:

// ISO-C++名字空间std中的默认范例参数值示例
{
 template <class T, class Container = deque<T> >
 class queue;

 template <class T, class Allocator = allocator<T> >
 class vector;
 // …
}

  你可以提供默认的元素范例,如下所示:

// 带有默认的元素范例的模板声明
template <class elemType=String^, int size=1024>
public ref class tStack {};

从设计的角度来说很难证明它的正确性,因为一般来说容器不集中中在在单个默认范例上。

   指针也可以作为非范例参数,因为工具或函数的地点在编译时就已知了,因此是一个常量表达式。譬喻,你大概但愿为仓库类提供第三个参数,这个参数指明碰着特定条件的时候利用的回调处理惩罚措施。明智地利用typedef可以大幅度简化那些外貌上看起来很巨大的声明,如下所示:

typedef void (*handler)( … array<Object^>^ );
template <class elemType, int size, handler cback >
public ref class tStack {};

  虽然,你可觉得处理惩罚措施提供默认值–在这个例子中,是一个已有的要领的地点。譬喻,下面的缓冲区声明就提供了巨细和处理惩罚措施:

void defaultHandler( … array<Object^>^ ){ … }

template < class elemType,
int size = 1024,
handler cback = &defaultHandler >
public ref class tStack {};

  由于默认值的位置序次优先于定名序次,因此假如不提供明晰的巨细值(纵然这个巨细与默认值是反复的),就无法提供重载的处理惩罚措施的。下面就是大概用到的修改仓库的要领:

void demonstration()
{
 // 默认的巨细和处理惩罚措施
 tStack<String^> ^ts1 = nullptr;
 // 默认的处理惩罚措施
 tStack<String^, 128> ^ts2 = gcnew tStack<String^, 128>;
 // 重载所有的三个参数
 tStack<String^, 512, &yourHandler> ^ts3;
}

  模板支持的第二种特另外参数就是template模板参数–也就是这个模板参数自己表示为一个模板。譬喻:

// template模板参数
template <template <class T> class arena, class arenaType>
class Editor {
arena<arenaType> m_arena;
// …
};

   Editor模板类列出了两个模板参数arena和arenaType。ArenaType是一个模板范例参数;你可以通报整型、字符串型、自界说范例等等。Arena是一个template模板参数。带有单个模板范例参数的任何模板类都可以绑定到arena。m_arena是一个绑定到arenaType模板范例参数的模板类实例。譬喻:

// 模板缓冲区类
template <class elemType>
public ref class tBuffer {};

void f()
{
 Editor<tBuffer,String^> ^textEditor;
 Editor<tBuffer,char> ^blitEditor;
 // …
}


#p#副标题#e#   范例参数约束

   假如你把参数化范例简朴地作为存储和检索元素的容器,那么你可以略过这一部门了。当你需要挪用某个范例参数(譬喻在较量两个工具,查察它们相等可能个中一个小于另一个的时候,可能通过范例参数挪用要领名称或嵌套范例的时候)上的操纵的时候,才会思量约束的问题。譬喻:
#p#分页标题#e#

template <class T>
ref class Demonstration {
 int method() {
  typename T::A *aObj;
  // …
 }
};

这段代码乐成地声明白aObj,它同时还约束了可以或许乐成地绑定到你的类模板的范例参数。譬喻,假如你编写下面的代码,aObj的声明就是犯科的(在这种特定的环境下),编译器会报错误信息:

int demoMethod()
{
 Demonstration<int> ^demi = gcnew Demonstration<int>( 1024 );
 return dm->method();
}

虽然,其特定的约束是,这个范例参数必需包括一个叫做A的范例的嵌套声明。假如它的名字叫做B、C或Z都没有干系。更普通的约束是范例参数必需暗示一个类,不然就不答允利用T::范畴操纵符。我利用int范例参数同时违反了这两公约束。譬喻,Visual C++编译器会生成下面的错误信息:

error C2825: ‘T’: must be a class or namespace when followed by ‘::’

C++模板机制受到的一条品评意见是:缺乏用于描写这种范例约束的形式语法(请留意,在参数化范例的原始设计图纸中,Bjarne Stroustrup阐述了曾经思量过提供显式约束语法,可是他对这种语法不太满足,并选择了在谁人时候不提供这种机制)。也就是说,在一般环境下,用户在阅读源代码或相关的文档,可能编译本身的代码并阅读随后的编译器错误动静的时候,才气意识到模板有隐含约束。

  假如你必需提供一个与模板不匹配的范例参数该怎么办呢?一方面,我们能做的工作很少。你编写的任何类都有必然的假设,这些假设表示为某些利用方面的约束。很难设计出适合每种环境的类;设计出适合每种环境和每种大概的范例参数的模板类越发坚苦。

  另一方面,存在大量的模板特性为用户提供了"迂回"空间。譬喻,类模板成员函数不会绑定到范例参数,直到在代码中利用该函数为止(这个时候才绑定)。因此,假如你利用模板类的时候,没有利用那些使范例参数失效的要领,就不会碰着问题。

  假如这样也不行行,那么还可以提供该要领的一个专门的版本,让它与你的范例参数关联。在这种环境下,你需要提供Demonstration<int>::要领的一个专用的实例,可能,更为普遍的环境是,在提供整数范例参数的时候,提供整个模板类的专门的实现方法。

  一般来说,当你提到参数化范例可以支持多种范例的时候,你一般谈到的是参数化的被动利用–也就是说,主要是范例的存储和检索,而不是努力地操纵(处理惩罚)它。

  作为模板的设计人员,你必需知道本身的实现对范例参数的隐含约束条件,而且尽力去确保这些条件不是多余的。譬喻,要求范例参数提供便是和小于操纵是公道的;可是要求它支持小于或便是或XOR位运算符就不太公道了。你可以通过把这些操纵解析到差异的接口中,可能要求特另外、暗示函数、委托或函数工具的参数来放松对操纵符的依赖性。譬喻,代码2显示了一个当地C++措施员利用内建的便是操纵符实现的搜索要领。

  代码2:倒霉于模板的搜索实现

#p#分页标题#e#

template <class elemType, int size=1024>
ref class Container
{
 array<elemType> ^m_buf;
 int next;

 public:
  bool search( elemType et )
  {
   for each ( elemType e in m_buf )
    if ( et == e )
     return true;
    return false;
  }

  Container()
  {
   m_buf = gcnew array<elemType>(size);
   next = 0;
  }

  void add( elemType et )
  {
   if ( next >= size )
    throw gcnew Exception;
    m_buf[ next++ ] = et;
  }

  elemType get( int ix )
  {
   if ( ix < next )
    return m_buf[ ix ];
   throw gcnew Exception;
  }
  // …
 };

  在这个搜索函数中没有任何错误。可是,它不太利于利用模板,因为范例参数与便是操纵符细密耦合了。更为机动的方案是提供第二个搜索要领,答允用户通报一个工具来举办较量操纵。你可以利用函数成员模板来实现这个成果。函数成员模板提供了一个特另外范例参数。请看一看代码3。

   代码3:利用模板

template <class elemType, int size=1024>
ref class Container
{
 // 其它的都沟通 …
 // 这是一个函数成员模板…
  // 它可以同时引用包括的类参数和自有参数…

 template <class Comparer>
 bool search( elemType et, Comparer comp )
 {
  for each ( elemType e in m_buf )
   if ( comp( et, e ) )
    return true;
 
   return false;
 }
 // …
};

此刻用户可以选择利用哪一个要领来搜索内容了:细密耦合的便是操纵符搜索效率较高,可是不适合于所有范例;较机动的成员模板搜索要求通报用于较量的范例。

   哪些工具合用这种较量目标?函数工具就是普通的用于这种目标的C++设计模式。譬喻,下面就是一个较量两个字符串是否相等的函数工具:

class EqualGuy {
 public:
  bool operator()( String^ s1, String^ s2 )
  {
   return s1->CompareTo( s2 ) == 0;
  }
};

代码4中的代码显示了你如何挪用这两个版本的搜索成员函数模板和传统的版本。

  代码4:两个搜索函数

#p#分页标题#e#

int main()
{
 Container<String^> ^sxc = gcnew Container<String^>;
 sxc->add( "Pooh" );
 sxc->add( "Piglet" );

 // 成员模板搜索 …
 if ( sxc->search( "Pooh", EqualGuy() ) )
  Console::WriteLine( "found" );
 else Console::WriteLine( "not found" );

  // 传统的便是搜索 …
  if ( sxc->search( "Pooh" ) )
   Console::WriteLine( "found" );
  else Console::WriteLine( "not found" );
}

一旦有了模板的观念,你就会发明利用模板险些没有什么工作不是实现。至少感受是这样的。

#p#副标题#e#

泛型约束

  与模板差异,泛型界说支持形式约束语法,这些语法用于描写可以正当地绑定的范例参数。在我具体先容约束成果之前,我们简短地思量一下为什么泛型选择了提供约束成果,而模板选择了不提供这个成果。我相信,最主要的原因是两种机制的绑按时间之间差别。

  模板在编译的进程中绑定,因此无效的范例会让措施遏制编译。用户必需当即办理这个问题可能把它从头处理惩罚成非模板编程方案。执行措施的完整性不存在风险。

  另一方面,泛型在运行时绑定,在这个时候才发明用户指定的范例无效就已经太迟了。因此通用语言布局(CLI)需要一些静态(也就是编译时)机制来确保在运行时只会绑定有效的范例。与泛型相关的约束列表是编译时过滤器,也就是说,假如违反的时候,会阻止措施的成立。

  我们来看一个例子。图5显示了用泛型实现的容器类。它的搜索要领假设范例参数衍生自Icomparable,因此它实现了该接口的CompareTo要领的一个实例。请留意,容器的巨细是在结构函数中由用户提供的,而不是作为第二个、非范例参数提供的。你应该记得泛型不支持非范例参数的。

  代码5:作为泛型实现的容器

generic <class elemType>
public ref class Container
{
 array<elemType> ^m_buf;
 int next;
 int size;
 
public:
 bool search( elemType et )
 {
  for each ( elemType e in m_buf )
   if ( et->CompareTo( e ))
    return true;
   return false;
 }

 Container( int sz )
 {
  m_buf = gcnew array<elemType>(size = sz);
  next = 0;
 }

 // add() 和 get() 是沟通的 …

};

该泛型类的实此刻编译的时候失败了,碰着了如下所示的致命的编译诊断信息:

error C2039: ‘CompareTo’ : is not a member of ‘System::Object’

你也许有点糊涂了,这是怎么回事?没有人认为它是System::Object的成员啊。可是,在这种环境下你就错了。在默认环境下,泛型参数执行最严格的大概的约束:它把本身的所有范例约束为Object范例。这个约束条件是对的,因为只答允CLI范例绑定到泛型上,虽然,所有的CLI范例都多几几何地衍生自Object。因此在默认环境下,作为泛型的作者,你的操纵很是安详,可是可以利用的操纵也是有限的。

  你大概会想,好吧,我减小机动性,制止编译器错误,用便是操纵符取代CompareTo要领,可是它却引起了更严重的错误:

error C2676: binary ‘==’ : ‘elemType’ does not define this operator
or a conversion to a type acceptable to the predefined operator

同样,产生的环境是,每个范例参数开始的时候都被Object的四个民众的要领困绕着:ToString、GetType、GetHashCode和Equals。其结果是,这种在单独的范例参数上列出约束条件的事情表示了对初始的强硬约束条件的慢慢放松。换句话说,作为泛型的作者,你的任务是凭据泛型约束列表的约定,回收可以验证的方法来扩展那些答允的操纵。我们来看看如何实现这样的事务。

   我们用约束子句来引用约束列表,利用非保存字"where"实现。它被安排在参数列表和范例声明之间。实际的约束包括一个或多个接口范例和/或一个类范例的名称。这些约束显示了参数范例但愿实现的可能衍生出范例参数的基类。每种范例的民众操纵荟萃都被添加到可用的操纵中,供范例参数利用。因此,为了让你的elemType参数挪用CompareTo,你必需添加与Icomparable接口关联的约束子句,如下所示:

generic <class elemType>
where elemType : IComparable
public ref class Container
{
  // 类的主体没有改变 …
};

#p#分页标题#e#

这个约束子句扩展了答允elemType实例挪用的操纵荟萃,它是隐含的Object约束和显式的Icomparable约束的民众操纵的团结体。该泛型界说此刻可以编译和利用了。当你指定一个实际的范例参数的时候(如下面的代码所示),编译器将验证实际的范例参数是否与将要绑定的范例参数的约束相匹配:

int main()
{
 // 正确的:String和int实现了IComparable
 Container<String^> ^sc;
 Container<int> ^ic;

 //错误的:StringBuilder没有实现IComparable
 Container<StringBuilder^> ^sbc;
}

编译器会提示某些违反了法则的信息,譬喻sbc的界说。可是泛型的实际的绑定和结构已经过运行时完成了。

  接着,它会同时验证泛型在界说点(编译器处理惩罚你的实现的时候)和结构点(编译器按拍照关的约束条件查抄范例参数的时候)是否违反了约束。无论在谁人点失败城市呈现编译时错误。

  约束子句可以每个范例参数包括一个条目。条目标序次不必然跟参数列表的序次沟通。某个参数的多个约束需要利用逗号分隔。约束在与每个参数相关的列表中必需独一,可是可以呈此刻多个约束列表中。譬喻:

generic <class T1, class T2, class T3>
where T1 : IComparable, ICloneable, Image
where T2 : IComparable, ICloneable, Image
where T3 : ISerializable, CompositeImage
public ref class Compositor
{
  // …
};

在上面的例子中,呈现了三个约束子句,同时指定了接口范例和一个类范例(在每个列表的末端)。这些约束是有特另外意义的,即范例参数必需切合所有列出的约束,而不是切合它的某个子集。我的同事Jon Wray指出,由于你是作为泛型的作者来扩展操纵荟萃的,因此假如放松了约束条件,那么该泛型的用户在选择范例参数的时候就得增加更多的约束。

  T1、T2和T3子句可以凭据其它的序次安排。可是,不答允超过两个或多个子句指定某个范例参数的约束列表。譬喻,下面的代码就会呈现违反语法错误:

generic <class T1, class T2, class T3>
// 错误的:同一个参数不答允有两个条目
where T1 : IComparable, ICloneable
where T1 : Image
public ref class Compositor
{
  // …
};

类约束范例必需是未密封的(unsealed)参考类(数值类和密封类都是不答允的,因为它们不答允担任)。有四个System名字空间类是克制呈此刻约束子句中的,它们别离是:System::Array、System::Delegate、 System::Enum和System::ValueType。由于CLI只支持单担任(single inheritance),约束子句只支持一个类范例的包括。约束范例至少要像泛型或函数那样容易会见。譬喻,你不能声明一个民众泛型并列出一个或多个内部可视的约束。

  任何范例参数都可以绑定到一个约束范例。下面是一个简朴的例子:

generic <class T1, class T2>
where T1 : IComparable<T1>
where T2 : IComparable<T2>
public ref class Compositor
{
  // …
};

约束是不能担任的。譬喻,假如我从Compositor担任获得下面的类,Compositor的T1和T2上的Icomparable约束不会应用在 BlackWhite_Compositor类的同名参数上:

generic <class T1, class T2>
public ref class BlackWhite_Compositor : Compositor
{
  // …
};

当这些参数与基类一起利用的时候,这就有几分设计方面的便利了。为了担保Compositor的完整性,BlackWhite_Compositor必需把Compositor约束流传给那些通报到Compositor子工具的所有参数。譬喻,正确的声明如下所示:

generic <class T1, class T2>
where T1 : IComparable<T1>
where T2 : IComparable<T2>
public ref class BlackWhite_Compositor : Compositor
{
  // …
};

包装

  你已经看到了,在C++/CLI下面,你可以选择CLR泛型或C++模板。此刻你所拥有的常识已经可以按照特定的需求作出明智的选择了。在两种机制下,逾越元素的存储和检索成果的参数化范例都包括了每种范例参数必需支持操纵的假设。

  利用模板的时候,这些假设都是隐含的。这给模板的作者带来了很大的长处,他们对付可以或许实现什么样的成果有很大的自由度。可是,这对付模板的利用者是倒霉的,他们常常面临某些大概的范例参数上的没有正式文档记实的约束荟萃。违反这些约束荟萃就会导致编译时错误,因此它对付运行时的完整性不是威胁,模板类的利用可以阻止失败呈现。这种机制的设计偏好倾向于实现者。

  利用泛型的时候,这些假设都被明明化了,并与约束子句中罗列的根基范例荟萃相关联。这对泛型的利用者是有利的,而且担保通报给运行时用于范例结构的任何泛型都是正确的。据我看来,它在设计的自由度上有一些约束,而且使某些模板设计习惯稍微难以受到支持。对这些形式约束的违反,无论使在界说点照旧在用户指定范例参数的时候,城市导致编译时错误。这种机制的设计偏好倾向于消费者.

    关键字:

在线提交作业