C++ Traits编程技法--从迭代器的设计看参数推导与类型推导-阿里云开发者社区

开发者社区> shy丶gril> 正文

C++ Traits编程技法--从迭代器的设计看参数推导与类型推导

简介:
+关注继续查看

迭代器与相应类型推导


在写作泛型函数或代码时,我们可能存在这样的需要:与参数相关的其它类型,比如一个迭代器的值的类型,在算法中运用迭代器时,很可能会用到其也叫相应类型(associate type)。

什么是相应类型?
迭代器所指之物的类型就是其中一个。如果我们的算法中有必要声明一个变量,以”迭代器所指对象的类别”为型号。

本文要向大家展示一个函数模板推导机制使用技法,这个在STL的迭代器和许多排序算法中广泛使用。

考虑一个情况,我们在写一个泛型函数,它接受一对迭代器,要做的事就是对这一对迭代器之间的元素进行排序,其中将出现这幕:我需要对两个值进行交换。不知道大家有没有写过这样的代码,现在的问题是如何实现这两个值的交换?

如:

if(*itr > *(itr+1))
{
    //交换两个迭代器指向的值
    //注意此时我们并不(*itr)的类型
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

很显然我们需要了解到迭代器所指向的具体类型,这该怎么好呢

毕竟如果C++没有只支持sizeof(),并不支持typeof()

有些人说了我们有RTT1机制中的typeid(),是的很好,但是那获取到的也只是类型名称name,而不是类型type,更不可能来做变量声明

关于RTT1机制中的typeid(),请参见C++ typeid关键字详解

解决方法一函数模版的参数推导机制


解决的方法:利用函数模版(function template)的参数推导(argument deducation)机制。

示例


///理解模板类型推导
#include <iostream>

template <class Iter, class T>
void func_impl(Iter iter, T t)

{
    T tmp;              //  这里解决了问题, T就是迭代器所指之物的类型, 本例为int

    /// ...  这里做原来func应该做的全部工作
}




template<class Iter>
inline func(Iter iter)
{
    func_impl(iter, *iter);
}


int main( )
{
    int i;
    func(&i);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

解析


参数推导机制,以func()为对外接口,却把实际的操作全部置于func_impl()之中,由于func_impl()是一个函数模版(function template),一旦被调用,编译器会自动进行template的参数推导,于是导出类型T的问题,顺利得到了解决。

问题


但是迭代器相应类型不只是“迭代器所指对象的类型”一种而已

根据经验,最常用的相应类型有5种,然而并非任何情况下任何一种都可利用函数模版的参数推导机制来取得。

因此我们需要更一般的解法。

解决方法二使用嵌套依赖类型(nested depended name)


也称为Traits编程技法,是STL中广泛使用的技巧

迭代器所指对象的类型,称为迭代器的value type,上述的参数类型推导技巧虽然可用于value type,却并不对所有的相应类型可用。

前面的情况,通过隐藏了中间层,通过函数模版自己去推导参数类型,我们的接口也只是关心迭代器而不在意其指向的,但是如果我们的对外接口不能不了解到其相应类型的时候,这种方法就不适用了。

比如如下情况,万一value type必须用于函数的返回值,就束手无策了。

毕竟函数的“template参数推导机制”推而导之的只是参数,无法推导函数的回返值类型。

我们需要其他方法,有什么好的方法呢,还记得之前提到的typename的以及嵌套依赖类型(nested depended name)么。

那不就可以么,我们不妨一试

参见 C++中typename和class在声明模板时的区别

示例


#include <iostream>
template <class T>
struct MyIter
{
    MyIter(T *p = NULL)
    :m_ptr(p)
    {
        /// NOP...
    }

    T& operator*( ) const
    {
        return *m_ptr;
    }



    typedef T value_type;       //  内嵌类型声明{nested type}
    T   *m_ptr;
};


template <class Iter>
typename Iter::value_type          ///  这一整行func的返回值类型
func(Iter iter)
{
    ///
    return *iter;
}


int main(void)
{
    MyIter<int> ite(new int(8));
    std::cout <<func(ite) <<std::endl;
    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

解析


func()的返回值被定义为typename Iter::value_type

内嵌类型被定义为 typedef T value_type

由于T是一个template参数,在它被编译器具体化之前,编译器对T一无所知,换句话说,编译器此时并不知道MyIter<T>::value_type代表的是一个类型还是一个成员函数或者说是一个数据成员,

而这个typename关键字的用意就在于告诉编译器这个是一个类型,如敝才能顺利通过编译。

问题


这样我们的问题似乎得到了解决,但是这里有一个隐晦的陷阱:并不是所有的迭代器都是类class type,比如有原生的指针就不是。如果不是class type,就无法为它定义内嵌类型。

但是这显然不是我们做模版类所期待的,在STL(甚至于整个泛型思维)绝对接受原生的指针作为一种迭代器,所有我们上面的方法还不够,有没有什么方法可以让上述的一般化概念针对特定情况(比如原生的指针类型)做特殊化处理呢?

特性萃取机器–偏序化实现支持原生类型


那么这就是template partial specialization可以完成的

模版特化问题


如果class template拥有一个以上的template参数,那么可以针对其中某个(或者多个,但是不能全部)的参数进行特化工作。

换句话说,我们可以在泛化设计中提供一个特化的版本。即通过将泛化版本中的某些template参数赋予明确的指定。

/// 泛化的C接收任意类型
template <typename T>
class C             //  这个泛化版本允许接受T为任意类型
{
    // NOP...
};


/// 特化的类C接收原生的指针作为对象
template <typename T>
class C<T*>             //  这个泛化版本适用于"T为原生指针的情况"
{
    //  T为原生指针便是T为任何型别的一个更进一步的条件限制
    // NOP...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

关于函数模版的特化问题,不是我们今天的重点,要了解详细信息,请参见
C++模板的特化详解(函数模版特殊,类模版特化)

通过针对任何template参数的模版,进行特化,得到一个限制了参数对象的特化版本。

有了这个利器,我们就可以解决前述的”内嵌类型”未能解决的问题,先前的问题是,原生的指针并非class,因此无法为他们定义内嵌型别。

现在我们可以通过对迭代器指针为参数的特化对象,来设计可以接收原生指针的特殊迭代器。

万能的中间层

因此我们设计出一个中间层,并添加一个value type内嵌对象

//  泛化的iterator_traits对象
template <typename Iter>
struct iterator_traits
{
    typedef typename I::value_type value_type;
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这个所谓的traits迭代器说明,如果Iter定义有自己的value type,那么就通过这个traits的作用,萃取出来的value_type成员就是Iter::value_type。

换句话说,如果I定义有自己的value type,那么先前的那个func就成为

//  之前的func
template <class Iter>
typename Iter::value_type          ///  这一整行func的返回值类型
func(Iter iter)
{
    ///
    return *iter;
}


//  新的func
template <class Iter>
//typename Iter::value_type          ///  这一整行func的返回值类型
typename iterator_traits<Iter>::value_type
func(Iter iter)
{
    ///
    return *iter;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

特化中间层以支持原生指针


增加iterator_traits这一中间层,现在我们为iterator_traits添加特化版本。

// 特化的iterator_traits接收<T*>参数
template<class T>
struct iterator_traits<T *>
{
    typedef T value_type;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通过这种方式,原生的指针比如int *虽然不是类对象class type,亦可通过traits取其value type。

于是我们的迭代器支持原生的指针类型了。

但是这样子够了么,请看下面的例子,

当我们使用指向const常数对象的指针时,看看发生了什么

iterator_traits<const int *>::value_type
  • 1
  • 1

获得到的是一个const int,而不是int。很显然这个并不是我们所期望?

比如我们利用这种方式来声明一个临时变量,使其类别与迭代器的value type相同,而现在,声明一个无法赋值(因const之故)的暂时变量,并没有什么卵用。

这一切的一切,就只是因为我们获取到的迭代器的value type是一个pointer-to-const

进一步特殊const


// 特化的iterator_traits接收<const T*>参数, 萃取出一个T型
template<class T>
struct iterator_traits<const T *>   /
{
    typedef T value_type;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

现在OK了,不管是针对迭代器MyIterm,还是原生的指针int ,甚至是const int ,都可以通过traits取出正确的value type

代码


///c++11 条款1:理解模板类型推导
///http://blog.csdn.net/coolmeme/article/details/43986163
///http://blog.csdn.net/shinehoo/article/details/5722362
///  STL源码剖析 PDF-119/534

#include <iostream>
#include <typeinfo>


#include <iostream>
template <class T>
struct MyIter
{
    MyIter(T *p = NULL)
    :m_ptr(p)
    {
        /// NOP...
    }

    T& operator*( ) const
    {
        return *m_ptr;
    }



    typedef T value_type;       //  内嵌型别声明{nested type}
    T   *m_ptr;
};


///// 泛化的C接收任意类型
//template <typename T>
//class C             //  这个泛化版本允许接受T为任意类型
//{
//    // NOP...
//};
//
//
///// 特化的类C接收原生的指针作为对象
//template <typename T>
//class C<T*>             //  这个泛化版本适用于"T为原生指针的情况"
//{
//    //  T为原生指针便是T为任何型别的一个更进一步的条件限制
//    // NOP...
//};


//  泛化的iterator_traits对象
template <typename Iter>
struct iterator_traits
{
    typedef typename Iter::value_type value_type;
};

// 特化的iterator_traits接收<T*>参数, 萃取出一个T类型
template<class T>
struct iterator_traits<T *>
{
    typedef T value_type;
};

// 特化的iterator_traits接收<const T*>参数, 萃取出一个T型
template<class T>
struct iterator_traits<const T *>
{
    typedef T value_type;
};


template <class Iter>
//typename Iter::value_type          ///  这一整行func的返回值类型
typename iterator_traits<Iter>::value_type
func(Iter iter)
{
    ///
    return *iter;
}



int main(void)
{

    MyIter<int> ite(new int(8));
    std::cout <<func(ite) <<std::endl;


    std::cout <<typeid(iterator_traits< MyIter<int> >::value_type).name();
    std::cout <<typeid(iterator_traits<int *>::value_type).name();
    std::cout <<typeid(iterator_traits<const int *>::value_type).name();


    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

最后通过traits这个特性萃取机角色,可以巧妙的萃取出各个迭代器的属性。

STL中迭代器的设计


这点我们可以在STL源码的找到对应的设计,参见 stl_iterator_base.h
由于

#ifdef __STL_USE_NAMESPACES
template <class _Category, class _Tp, class _Distance = ptrdiff_t,
          class _Pointer = _Tp*, class _Reference = _Tp&>
struct iterator {
  typedef _Category  iterator_category;
  typedef _Tp        value_type;
  typedef _Distance  difference_type;
  typedef _Pointer   pointer;
  typedef _Reference reference;
};
#endif /* __STL_USE_NAMESPACES */

#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION

template <class _Iterator>
struct iterator_traits {
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};

template <class _Tp>
struct iterator_traits<_Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef _Tp*                        pointer;
  typedef _Tp&                        reference;
};

template <class _Tp>
struct iterator_traits<const _Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;
  typedef const _Tp&                  reference;
};


转载:http://blog.csdn.net/gatieme/article/details/50950726

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
MySQL HA架构下innodb_flush_log_at_trx_commit及sync_binlog参数
      HeartBeat + DRBD以及MySQL replication是很多企业比较普遍使用的方式。对于数据的完整性和一致性的问题,这两种架构需要考虑2个重要的参数innodb_flush_log_at_trx_commit以及sync_binlog参数。
1113 0
Python编程语言学习:利用locals函数判断某个变量参数之前是否已经被定义/存在/出现
Python编程语言学习:利用locals函数判断某个变量参数之前是否已经被定义/存在/出现
15 0
C#编程-121:文件保存之SaveFileDialog控件
C#编程-121:文件保存之SaveFileDialog控件
21 0
ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!)
原文:ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!) ORACLE PL/SQL编程之六: 把过程与函数说透(穷追猛打,把根儿都拔起!)   继上篇:ORACLE PL/SQL编程之八:把触发器说透 得到了大家的强力支持,感谢。
760 0
《OpenACC并行程序设计:性能优化实践指南》一 3.12 参考文献
本节书摘来自华章出版社《OpenACC并行程序设计:性能优化实践指南》一 书中的第3章,第3.12节,作者:[美] 罗布·法伯(Rob Farber),更多章节内容可以访问云栖社区“华章计算机”公众号查看。
585 0
Linux系统中使用GCC CPU参数优化代码编译
Linux系统中使用GCC CPU参数优化代码编译 使用特定的GCC参数可以使编译出的程序执行效率有较大提升。具体如下: 1、优化原理: 在编译程序时,借助参数传递的方法,使用与系统CPU相匹配的gcc参数,编译出的程序就是为系统CPU而进行特定优化过的,因而执行速度和效率都会是最好。
733 0
xcode反汇编调试iOS模拟器程序(三)查看Objective-C函数与参数
在Objective-C函数的入口处(第一行)加断点,可用esp指针来探查参数。 以esp为基址,往后的偏移分别是: 0:函数执行完毕后的返回地址(不是返回值的地址哦) 4:对象实例的指针,即self指针 8:selector,实际是一个...
973 0
[软考考点解析]软件设计师--主存与Cache地址映射方式
1. 题目 主存与Cache的地址映射方式中,____方式可以实现任意主存的任意一块装入Cache中任意位置,只有装满才需要替换。 A 全相联 B 直接映射 C 组相联 D 串并联
6 0
+关注
1878
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载