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; };