读书人

【C++泛型编程】模板偏特化跟型别映射

发布时间: 2012-09-11 10:49:04 作者: rapoo

【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有什么学习泛型编程好的教材或者书推荐下的么

读书人网 >编程

热点推荐