1.模板偏特化
模板偏特化是让你在template的所有可能实体中特化出一组子集。
下面是一个模板全特化的例子,假设有一个类模板,名为Widget:
template<class Window,class Controller>
class Widget
{
....各种操作.....
};
特化的情况如下:
template<>
class Widget<ModalDialog,MyController>
{
...各种特化后的操作...
};
其中ModalDialog,MyController是另外定义的类。
有时候想针对任意的Window并搭配固定的MyController来特化Widget,这时候就需要模板偏特化机制:
template<class Window> //Window仍然是泛化
class Widget<Window,MyController> // MyController是特化
{
.....;
};
偏特化的特性非常强大,当你具现化一个template时,编译器会把目前存在的偏特化和全特化作比较,并找出最匹配的。这种偏特化机制不能用在函数身上(不管是否为成员函数)。
注:a.你可以全特化class template中的成员函数,但是不能偏特化。
b.你不能偏特化namespace-level(非成员函数)函数。(可以运用Int2Type和Type2Type工具实现)
template<class T,class U> T Fun(U obj);//模板函数
template<class U> void Fun<void,U>(U obj)//错误,不能偏特化
template<class T> T Fun(Window obj); //正确,是函数重载
2.局部类
局部类可以定义如下:
void Fun()
{
class Local
{
...member variables...
};
...code using Local...
};
局部类不能定义static成员变量,也不能访问非static局部变量。局部类可以在template函数中使用。定义于template函数内的局部类可以使用函数的template参数作为其成员变量。
如下一个例子:有一个MakerAdapter 模板函数,可以将某个接口转接为另一个接口。它是在局部类的辅助下完成这一个接口的转换,这个局部类有泛化型别的成员。
class Interface
{
pubic:
virtual void Fun()=0;
};
template<class T,class P>
Interface * MakeAdapter(const T& obj,const P& arg)
{
class Local:public Interface
{
public:
Local(const T& obj,const P& arg):obj_(obj),arg_(arg){};
virtual void Fun()
{
obj_.Call(arg_);
}
private:
T obj_;
P arg_;
};
return new Local(obj,arg);
}
任何使用局部类的地方,都可以改用函数外的模板类来完成,并不一定要使用local class.但是局部类可以提高符号的地域性。外界不能继承一个隐藏在函数内的类。
3.常整数映射为型别(Int2Type)
template<int v>
struct Int2Type
{
enum{value=v};
};
Int2Type会根据参数v来产生不同的型别。这是因为不同的template具现体就是不同的型别。如Int2Type<0>和Int2Type<1>是不同的型别。这样一来就可以根据编译期计算出来的结果选用不同的函数,可以运用这个常数达到静态分派。
使用Int2Type的两个条件:
a.有必要根据某个编译期常数调用一个或多个不同的函数。
b.有必要在编译期实施分派。
若打算在执行期进行分派可以使用if-else或swith语句。大部分时候他们的执行成本是微不足道,但是有时候你不能那么做,因为if-else语句要求每一个分支都得编译成功,即使该条件测试在编译期才知道。
下面是错误的代码:因为型别T没有Clone()函数,就会编译出错
template <typename T, bool isPolymorphic>
class MyContainer
{
public:
void DoSomething( T* p)
{
if ( isPolymorphic )
{
T *newPtr = p->Clone();
// ...
}
else
{
T *newptr = new T(*p);
// ...
}
}
// ...
}
最好的解决办法是利用Int2Type进行函数重载
template <typename T, bool isPolymorphic>
class MyContainer
{
private:
void DoSomething( T* p, Int2Type<true>)
{
T* newptr = p->Clone();
// ...
}
void DoSomething( T* p, Int2Type<false>)
{
T* newptr = new T(*p);
// ...
}
public:
void DoSomething( T* p)
{
DoSomething( p, Int2Type<isPolymorphic>());
}
};
这个小技巧之所以有用,是编译器并不会去编译一个未被使用到的template函数。只会对它做文法检查。
4.型别对型别的映射(Type2Type)
由于不存在template函数的偏特化,如果想模拟出类似的机制怎么办呢?
如下的程序:
template <class T, class U>
T *Create(const U& arg)
{
return new T(arg);
}
现在假设Widget对象是你碰不到的老代码,它需要两个参数才能构造出对象来,第二个参数固定为-1.如果派生类则没有这个问题。
现在该如何特化Create(),让它处理独特的Widget呢? 一个明显的方案是写出一个CreateWidget()来专门处理,但是这样就没有一个统一的接口来生成Widgets和其派生对象。
由于无法偏特化一个函数,下面的写法也是错误的:
template <class U>
Widget *Create<Widget, U>(const U& arg)
{
return new Widget(arg, -1);
}
由于函数缺乏偏特化机制,因此只能用重载的方式实现:
template <class T, class U>
T *Create( const U& arg, T) // T is dummy
{
return new T(arg);
}
template <class U>
Widget *Create( const U& arg, Widget) // Widget is dummy
{
return new Widget(arg,-1);
}
问题:但是这种解法会很轻易构造未被使用的复杂对象,造成额外开销。这是我们可以使用Type2Type来解决,它的定义如下:
template <typename T>
struct Type2Type
{
typedef T OriginalType;
};
template <class T, class U>
T *Create( const U& arg, TypeToType<T>)
{
return new T(arg);
}
template <class U>
Widget *Create( const U& arg, TypeToType<Widget>)
{
return new Widget(arg, -1);
}
String *pS = Create("Hello", Type2Type<String>());
Widget *pW = Create( 200, Type2Type<Widget>());
Create()的第二个参数只是用来选择适当的重载函数,可以令各种Type2Type实体对应程序中的各种型别,并根据不同的Type2Type实体来特化Create().
5.型别选择
有时候泛型程序中需要根据一个bool变量来选择某个型别或另一个型别。
在MyContainer的例子中,你可能会以一个std::vector作为存储结构,面对多态型别,你不能存储对象实体,只能存储指针。对于非多态型别,可以存储实体,这样比较有效率。
template <typename T, bool isPolymorphic>
class MyContainer
{
// store pointers in polymorphic case: vector<T *>
// store values otherwise: vector<T>
};
你需要根据isPolymorphic来决定ValueType定义为T *还是T.可以使用如下Traits 类模板的方法来定义:
template <typename T, bool isPolymorphic>
struct MyContainerValueTraits
{
typedef T* ValueType;
};
template <typename T>
struct MyContainerValueTraits< T, false>
{
typedef T ValueType;
};
template <typename T, bool isPolymorphic>
struct MyContainer
{
typedef MyContainerValueTraits<T,isPolymorphic> Traits;
typedef typename Traits::ValueType ValueType;
// ...
vector<ValueType> v;
};
问题:上面的做法其实很笨拙难用,此外也无法扩充:针对不同的型别的选择,你必须定义出专属的Traits类模板。
Loki库中提供了Select 类模板可以使型别的选择立时可用。它采用偏特化机制:
template <bool Flag, typename T, typename U>
struct Select
{
typename T Result;
};
template <typename T, typename U>
struct Select<false, T, U>
{
typename U Result;
};
其运作方式是:如果Flag为True,编译器会使用第一份泛型定义,因此Result会被定义成T.如果Flag为False.那么偏特化机制会运作,于是Result被定义为U.现在可以很方便的定义 MyContainer::ValueType了。
template <typename T, bool isPolymorphic>
struct MyContainer
{
typedef typename Select<isPolymorphic, T*, T>::Result ValueType;
// ...
vector<ValueType> v;
};