【C++泛型编程】模板偏特化和型别映射(Int2Type,Type2Type)以及型别选择
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;};
- 1楼zhy006昨天 23:42
- lz有什么学习泛型编程好的教材或者书推荐下的么