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


 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值