模版元编程之——Type Traits

元编程之中有两种元数据,一种是类型数据,这里的type traits就是对类型数据的操作或者称为计算。这种技术在STL的设计中使用的非常广泛。本质上是借用C++模版提供的模版形参推导和特化两种机制来实现的。

迭代器

迭代器是联系算法与容器的桥梁,二者彼此独立设计,都使用泛型方法实现。迭代器就是联系他们的胶着剂。以经典的交换函数swap来入手:这个函数是一个模版函数,接收两个迭代器模版参数,将他们所指之物进行交换。
为了得到迭代器所指之物的类型,就需要使用了模版参数的自动推导来实现,因为C++不提供typeof操作,typeid也只能获取类型名称而不能拿来当做变量声明使用。
为了实现异常安全性代码,通常的实现方式是copy-and-swap方式,代码如下:

template<typename Ite1, typename Ite2, typename V>
void swap(Ite1 i1, Ite2 i2, V)
{
    V tmp(*i1);
    *i1 = *i2;
    *i2 = tmp;
}
//调用方式
list<int> a; vector<int> b;
swap(a.begin(), b.begin(), *a);

交换的两个迭代器所指的类型必须相同,上述调用中都是指向int类型的迭代器,迭代器类型可以不同也可以相同。从上述例子中可以看出,swap的第三个模版参数V就是用来进行tmp变量声明使用,并无其他作用,这正是利用了模版参数自动推导实现的。
但是,上述模版函数实现的第三个参数太丑陋。因此,最好将这个类型放到迭代器内部,只需要传入两个迭代器给swap函数。这正是type traits(类型萃取)的来历。

类型萃取

将上述实现代码进行改进,得到如下的经过萃取后的代码:

template<typename Ite>
struct iterator_traits{
    typedef typename Ite::value_type value_type;
    //其他萃取类型,如reference,pointer等...
};

template<typename Ite1, typename Ite2>
void swap(Ite1 i1, Ite2 i2)
{
    typename iterator_traits<Ite1>::value_type
    tmp(*i1);
    *i1 = *i2;
    *i2 = tmp;
}

上述实现中的iterator_traits就是一个萃取迭代器类型的包装模板类,它要求每个迭代器自己定义内嵌的typedef类型定义,支持value_type、reference等多种类型。
可以看出,iterator_traits仅仅是将各个迭代器自定义的内嵌类型做了一次转接或者包装,我们可以不用这个包装,而直接使用迭代器的内嵌类型也是可以的:

template<typename Ite1, typename Ite2>
void swap(Ite1 i1, Ite2 i2)
{
    typename Ite1::value_type tmp(*i1);
    *i1 = *i2;
    *i2 = tmp;
}

但是,对于原生指针、引用等类型,是无法使用上述方式进行的,这就体现了使用iterator_traits包装的作用,因为模板类可以进行偏特化,专门针对引用、指针等实现特化版本。

template<typename Ite>
struct iterator_traits<Ite*>{ //针对原生指针的偏特化
    typedef Ite value_type;
    typedef Ite * pointer;
    typedef Ite & reference;
    typedef ptrdiff_t difference_type;
    //...
};
template<typename Ite>
struct iterator_traits<const Ite*>{ //针对常量指针的偏特化
    typedef Ite value_type;
    typedef Ite * pointer;
    typedef Ite & reference;
    typedef ptrdiff_t difference_type;
    //...
};

高效swap

除了前面所述的对迭代器所指类型的萃取之外,还有一个问题就是,如果某些迭代器所指类型是很大的对象,创建一个临时变量并进行拷贝会非常耗时,需要进行动态内存分配和释放。这里就有一个是否是通过pimpl手法实现的问题,如果是就只需要交换简单的指针而不用创建对象,而交换指针只需要用标准库提供的std::swap函数进行高效交换即可。因此,这里需要使用偏特化来实现条件选择,如果两个迭代器类型相同,而且是所指类型是指针或者引用就可以进行高效的交换。
首先是判断迭代器类型以及所指类型,这就是用偏特化来实现if-else语句。

template<typename T, typename U>
struct is_same{
    const static int value = 0;
};
template<typename T>
struct is_same<T, T>{
    const static int value = 1;
};

template<typename T>
struct is_pointer{
    const static int value = 0;
};
template<typename T>
struct is_pointer<T *>{
    const static int value = 1;
};
//is_reference与上述实现类似

这样就可以对swap模版函数进行高效实现,其中对使用两个版本的函数又使用全特化进行了一次转发。

template<typename Ite1, typename Ite2>
void swap(Ite1 i1, Ite2 i2){
    typedef typename iterator_traits<Ite1> itetraits1;
    typedef typename iterator_traits<Ite2> itetraits2;
    typedef typename itetraits1::value_type v1;
    typedef typename itetraits1::reference_type r1;
    typedef typename itetraits1::value_type v1;
    typedef typename itetraits1::reference_type r2; 
    const bool tag = is_same<v1, v2> &&
         is_reference<r1> && is_reference<r2>;
    swap_impl<tag>(i1, i2);
}

template<bool tag>
struct swap_impl{
    template<typename Ite1, typename Ite2>
    swap_impl(Ite1 i1, Ite2, i2){
        typename iterator_traits<Ite1>::value_type tmp(*i1);
        *i1 = *i2;
        *i2 = tmp;
    }
};

template<>
struct swap_impl<true>{
    template<typename Ite1, typename Ite2>
    swap_impl(Ite1 i1, Ite2, i2){
        std::swap(*i1, *i2);
    }
};

上述计算tag就是判断,当两个迭代器所指类型相同,且所指对象的引用为真的引用时,可以不用建立临时对象,进行高效交换操作即可。

Type Traits

上述实现的对迭代器的所指类型的特性萃取,以及所指类型是否一样、是否是真的引用类型等实现,就是type traits。这是一种通过C++的模版进行模版实参推演在编译阶段实现的,巧妙利用全特化、偏特化,本质上就是使用元编程实现了条件语句,如果是上述的tag或者is_same这种两种结果的就是if-else语句,如果条件多于两个,如iterator_traits针对指针、引用、常量引用等多种特化版本,那么就是switch语句的实现相对应。据此,就可以很容易的理解各种type_traits的原理并进行运用了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值