模版元编程之——Type Traits

来源:互联网 发布:网络销售授权书模板 编辑:程序博客网 时间:2024/06/10 01:45

元编程之中有两种元数据,一种是类型数据,这里的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的原理并进行运用了。

0 0
原创粉丝点击