C++/CLR泛型与C++模板之间的对比
类型参数约束
如果你把参数化类型简单地作为存储和检索元素的容器,那么你可以略过这一部分了。当你需要调用某个类型参数(例如在比较两个对象,查看它们相等或者其中一个小于另一个的时候,或者通过类型参数调用方法名称或嵌套类型的时候)上的操作的时候,才会考虑约束的问题。例如:
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:不利于模板的搜索实现
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:两个搜索函数
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" );
}
一旦有了模板的概念,你就会发现使用模板几乎没有什么事情不是实现。至少感觉是这样的。
