Alexandrescu
最初设计的一个简单的模版,现在成了泛型设计的常用手法:
template
<int v>
struct
Int2Type {
enum { value = v };
};
对于每一个不同的常整数,
Int2Type
都代表不同的类型。这是因为不同的模版实例化都代表不同的类型,也就是说
Int2Type<0>
和
Int2Type<1>
是完全不同的。
当你想根据编译时结果来进行某些抉择——例如选择不同的函数——时,你可以依赖一个常整数来帮你完成分派工作,这时
Int2Type
便可以帮你是实现这个方法。
一般来说,你在下面两个情况中需要使用
Int2Type
:
l
你需要根据编译时常量来调用不同的函数
l
你需要在编译时执行分派工作
如果是在运行时执行分派工作,你可以用
if-else
或
switch
语句来简单的实现。在大部分的时候,这种运行时成本都是微不足道的。但是,有时它们却不能满足你的要求。既是是在编译期可以决定其分支,编译器还是会勤劳的为你编译其所有的分支,这也就意味着
if-else
的所有分支必须被成功编译。有些困惑?继续看下去:
考虑下面的情形:你设计了一个泛型容器
NiftyContainer
:
template
<class T> class NiftyContainer {
...
};
令
NiftyContainer
容器包含指向
T
对象的指针。为了复制
NiftyContainer
中的一个对象,你可能需要调用
T
的拷贝构造函数(对于非多态类型)或者一个名为
Clone()
的虚函数(对于多态类型)。你可以通过设置一个
bool
类型的模版参数来从类的客户手里获得关于多态的信息。
template
<class T, bool isPolymorphic> class NiftyContainer {
// Other actions
void DoSomething() {
T* pSomeObj = ...;
if(isPolymorphic) {
T* pNewObj = pSomeObj->Clone();
// Some polymorphic algorithm
}
else {
T* pNewObj = new T(*pSomeObj);
// Some non-polymorphic algorithm
}
}
};
问题在于编译器不会让你侥幸编译上面的代码。例如,如果一个多态类型没有定义
Clone()
,那么
NiftyContainer::DoSomething
绝对不会通过编译。尽管在编译时我们肯定可以对于分支进行判断,但这毕竟不是编译器的工作,他只会勤劳的为你编译出所有的代码。于是当你试图调用
NiftyContainer<int, false>::DoSomething
的时候,编译器还是会停在
pObj->Clone()
上,并且抱怨说:“你在做什么?”
对于非多态类型分支,也有可能发生编译错误。如果
T
是一个多态类型,并且把它的拷贝构造函数设定为
private
的时候(这时一个多态类的良好行为),非多态分支的
new T(*pObj)
就会发生错误。
你可能会想,如果编译器可以不去理会那些不必要的分支就好了,但是看来不太可能。那么,如何是好呢?
其实,方法有很多,
Int2Type
提供了一个简洁的办法。它可以根据
true
和
false
来生成两个不同的类型,而后根据
Int2Type<isPolymorphic>
评估正确的调用。
template
<class T, bool isPolymorphic> class NiftyContainer {
private
:
// Other actions
void DoSomething(T* pObj, Int2Type<true>) {
T* pNewObj = pSomeObj->Clone();
// Some polymorphic algorithm
}
void DoSomething(T* pObj, Int2Type<false>) {
T* pNewObj = new T(*pSomeObj);
// Some non-polymorphic algorithm
}
public
:
void DoSomething(T* pObj) {
DoSomething(pObj, Int2Type<isPolymorphic>());
}
};
当你想把常整数用作一个类型的时候,
Int2Type
是非常方便的。你可以传递一个临时的变量来重载函数。而之所以我们可以这样做,是因为编译器不会去编译没有用到的模板函数。