在算法中运用迭代器时,很可能用到其相应类型。什么是相应类型?迭代器所指对象的类型便是其中一个。我曾有一个错误的理解,那就是认为相应类型就是迭代器所指对象的类型,其实不然,相应类型是一个大的类别,迭代器所指对象的类型只是里面的其中一个。后面会讨论到相应类型的另外几种。

  假设算法需要声明一个变量,以“迭代器所指对象的类型”为类型,该怎么做?或许我们可能会想到RTTI性质中的typeid(),但获得的只是类型名称,并不能拿来声明变量。

  其中一个解决方法是:利用模版函数中的参数推导(argument deducation)机制:

  1. template <typename I, typename T>
  2. void func_impl(I iter, T t)
  3. {
  4. T tmp;
  5. //...做原本func()应该做的全部工作
  6. }
  7.  
  8. template<typename I>
  9. inline void func(I iter)
  10. {
  11. func_impl(iter, *iter);
  12. }

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

  然而上面也有提到,迭代器的相应类型不只是迭代器所指对象的类型一种而已。根据经验,最常用的相应类型有5种,然而并非任何情况下任何一种都可以利用上述的参数推导机制来取得。我们需要更全面的解法。

Traits编程技法

  迭代器所指对象的类型,称为该迭代器的value type。上述的参数类型推导虽然可以用于value type,但并不是什么情况都能用,例如需要把value type当作函数的返回值,这个办法就行不通了,毕竟模版函数的参数推导机制只能推导参数,无法推导函数的返回值类型。

  我们需要其他方法,为类型T定义一个别名似乎是一个好主意,例如:

  1. template<typename I>
  2. inline void func(I iter)
  3. {
  4. func_impl(iter, *iter);
  5. }
  6.  
  7. template<typename T>
  8. struct MyIter
  9. {
  10. typedef T value_type;
  11. T* ptr;
  12. MyIter(T* p = ) :ptr(p){}
  13. T& operator*()const { return *ptr; }
  14. //...
  15. };
  16.  
  17. template<typename T>
  18. typename I::value_type func(I ite)
  19. {
  20. return *ite;
  21. }
  22.  
  23. MyIter<int> ite(new int());
  24. cout << func(ite); //输出 8

  T是一个模板参数,在它被编译器具现化之前,编译器对T一无所知,换句话说,编译器此时并不知道MyIter<T>::value_type代表的是一个类型或是一个成员函数或是一个数据成员变量。关键词typename的用意在于告诉编译器这是一个类型,如此才能顺利通过编译。

  这似乎可行,但有一点需要注意,并不是所有的迭代器都是类的对象,例如原生指针,如果不是类对象,就无法为它定义别名。但STL绝对必须接受原生指针作为一种迭代器,所以上面这样还不够。那么有没有办法可以让上述的一般化操作针对特定情况做特殊化处理呢?

  模版偏特化可以做到,如果模板类拥有一个以上的模版参数,我们可以针对其中的某个或数个模版参数进行特化。换句话说,我们可以在泛化设计中提供一个特化版本,指明某些模板参数。假设有一个模版类如下:

  1. template<typename T>
  2. class C
  3. {...};

  偏特化的字面意思容易误导我们,让我们以为所谓的偏特化版本是对参数V指定某个参数值。其实不然,所谓的partial specialization的意思是提供另一份模板定义式,而其本身仍为模版。由此,面对上述这个模板类,我们可以提供另外一份定义:

  1. template<typename T>
  2. class C<T*> //这个特化版本仅适用于T为原生指针的情况
  3. {...};

  至此,对于value_type而言,我们有了比较好的提取类型的解决方法。那么Traits编程也已呼之欲出了,何为traits,我的理解就是通过一定的手段把某个类的特征提取出来,在侯捷老师的书中就叫萃取类的特性,何为类的特征或特性?这里可以分为两个类别去阐述,一是迭代器类,二是普通类,两个类别对应的特征不同,本节主要讲迭代器类的特征(也就是前面所述的相应类型,value_type就是其中一种),之后的章节再讲普通类的特征及其提取。而迭代器的相应类型最常用到的有五种,分别是:value type, difference type, pointer, reference, iterator catagoly。

迭代器相应类型之一:value type

  所谓value type,是指迭代器所指对象的类型。任何一个打算与STL算法有完美搭配的类,都应该定义自己的value type,做法如上所述。

迭代器相应类型之二:difference type

  用来表示两个迭代器之间的距离,因此可以用它来表示一个容器的最大容量,因为对于连续空间的容器而言,头尾之间的距离就是其最大容量。其实我有点不太理解,为什么要为两个迭代器之间的距离刻意定义一个别名,例如STL的count(),其传回值必须使用迭代器的difference type:

  1. template<typename I, typename T>
  2. typename itertor_traits<I>::difference_type //函数返回值,是个类型
  3. count(I first, I last, const T& value) {
  4. typename itertor_traits<I>::difference_type n = ;
  5. for ( ; first != last; ++first)
  6. {
  7. if (*first == value)
  8. ++n;
  9. }
  10. return n;
  11. }

  实际上定义一个整型变量来记录也是可以的,所以我不太明白difference type的用意。

  于原生指针而言,也有类似意义的类型ptrdiff_t(定义于<cstddef>头文件),所以在特化版本中,就把ptrdiff_t作为原生指针的difference_ type:

  1. template<typename I>
  2. struct iterator_traits
  3. {
  4. ...
  5. typedef typename I::difference_type difference_type;
  6. };
  7. //针对原生指针而设计的偏特化版本
  8. template<typename T>
  9. struct iterator_traits<T*>
  10. {
  11. ...
  12. typedef ptrdiff_t difference_type;
  13. };
  14. //针对原生的pointer-to-const而设计的偏特化版本
  15. struct iterator_traits<const T*>
  16. {
  17. ...
  18. typedef ptrdiff_t difference_type;
  19. };

迭代器相应类型之三:reference type

  我的理解是,reference type就是表示该类型变量的引用表示,例如要是想返回某迭代器所指类型变量的引用(需要用作左值时),用reference type就好了。侯捷书中并未对reference type作过多的解释,而是把注意力放在了T&和const T&上,即constant iterators(不允许改变所指对象的内容)和mutable iterators(允许改变所指对象的内容)。当p是个mutable iterator时,其value type是T,那么reference type就应该是T&;如果p是一个constant iterators,其value type是T,那么reference type就应该是const T&。实现的细节将在下节给出iterator源码时讨论。

迭代器相应类型之四:pointer type

  我的理解是,pointer type就是表示该类型变量的地址表示,也就是说,我们能够传回一个pointer,指向迭代器所指之物。这些相应类型已在先前的ListIter class中出现过:

  1. Item& operator*()const { return *ptr; }
  2. Item* operator->()const { return ptr; }

  Item&便是ListIter的reference type,而Item* 便是其pointer type。现在我们可以把reference type和pointer type这两个相应类型加入traits内:

  1. template<typename I>
  2. struct iterator_traits
  3. {
  4. ...
  5. typedef typename I::pointer pointer;
  6. typedef typename I::reference reference;
  7. };
  8. template<typename T>
  9. struct iterator_traits<T*>
  10. {
  11. ...
  12. typedef T* pointer;
  13. typedef T& reference;
  14. };
  15. struct iterator_traits<const T*>
  16. {
  17. ...
  18. typedef const T* pointer;
  19. typedef const T& reference;
  20. };

  而由于迭代器相应类型之五占据的篇幅过多,而本节讲的东西已经有点多了,所以把它放在下节再进行学习。

STL源码剖析——iterators与trait编程#2 Traits编程技法的更多相关文章

  1. STL源码剖析——iterators与trait编程#4 iterator源码

    在前两节介绍了迭代器的五个相应类型,并讲述如何利用traits机制提取迭代器的类型,但始终是把iteartor_traits类分割开来讨论,这影响我们的理解,本节将给出iteator的部分源码,里面涵 ...

  2. STL源码剖析——iterators与trait编程#3 iterator_category

    最后一个迭代器的相应类型就是iterator_category,就是迭代器本身的类型,根据移动特性与实行的操作,迭代器被分为了五类: Input Iterator:这种迭代器所指的对象,不允许外界改变 ...

  3. STL源码剖析——iterators与trait编程#1 尝试设计一个迭代器

    STL的中心思想在于:将数据容器与算法分开,独立设计,再用一帖粘着剂将它们撮合在一起.而扮演粘着剂这个角色的就是迭代器.容器和算法泛型化,从技术角度来看并不困难,C++的模板类和模板函数可分别达成目标 ...

  4. STL源码剖析 迭代器(iterator)概念与编程技法(三)

    1 STL迭代器原理 1.1  迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...

  5. STL源码剖析——Iterators与Traits编程#5 __type_traits

    上节给出了iterator_traits以及用到traits机制的部分函数的完整代码,可以看到traits机制能够提取迭代器的特性从而调用不同的函数,实现效率的最大化.显然这么好的机制不应该仅局限于在 ...

  6. 《STL源码剖析》学习之traits编程

    侯捷老师在<STL源码剖析>中说到:了解traits编程技术,就像获得“芝麻开门”的口诀一样,从此得以一窥STL源码的奥秘.如此一说,其重要性就不言而喻了.      之前已经介绍过迭代器 ...

  7. 【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub : https://github.com/rongweihe/Mor ...

  8. STL源码剖析之序列式容器

    最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...

  9. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

随机推荐

  1. Docker配置文件详解

    先来看一份 docker-compose.yml 文件,不用管这是干嘛的,只是有个格式方便后文解说: version: '2' services: web: image: dockercloud/he ...

  2. css去除图片间隙

    问题如下图 把图片转换成块状元素即可 .img{ display: block; } 解决后:

  3. 机器不学习:CNN入门讲解-为什么要有最后一层全连接

    哈哈哈,又到了讲段子的时间 准备好了吗? 今天要说的是CNN最后一层了,CNN入门就要讲完啦..... 先来一段官方的语言介绍全连接层(Fully Connected Layer) 全连接层常简称为 ...

  4. QTextToSpeech Win7奔溃

    在linux下,它是调用speech-dispatcher.在其它不同的平台上,调用各自平台的TTS引擎.所以在使用的时候,要确保本地的TTS引擎是可用的. 本地TTS引擎不可用可能会在声明QText ...

  5. [English]常用中英文对照表

    Always have been 一直如此 accordingly:相应地 assumption:假定 brace:大括号 branket:中括号 comma:逗号MISC:Miscellaneous ...

  6. Tosca 给定义变量,取内容放到变量里

    可以在TOOLS里 buffer viewer里面搜索查自己的变量

  7. Flutter -------- Drawer侧滑

    侧滑菜单在安卓App里面非常常见 抽屉通常与Scaffold.drawer属性一起使用.抽屉的子项通常是ListView,其第一个子项是DrawerHeader ,它显示有关当前用户的状态信息.其余的 ...

  8. 007 搜索API

    1.说明 这个API用于在elasticsearch中搜索内容,用户可以通过发送以查询字符串为参数的get请求进行搜索,也可以在post请求的消息体中进行查询. 2.多索引 允许搜索所有的索引或某些特 ...

  9. Springboot配置文件获取系统环境变量的值

    注意,这里说的是获取系统环境变量的值,譬如Windows里配置的JAVA_HOME之类的,可以直接在Springboot的配置文件中获取. 我们经常使用一些docker管理平台,如DaoCloud.r ...

  10. APP:目录

    ylbtech-APP:目录 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部     6.返回顶部     7.返回顶部   8.返回顶部   9.返回顶部   ...