STL源码剖析——iterators与trait编程#2 Traits编程技法

时间:2022-10-19 23:05:31

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

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

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

 template <typename I, typename T>
void func_impl(I iter, T t)
{
T tmp;
//...做原本func()应该做的全部工作
} template<typename I>
inline void func(I iter)
{
func_impl(iter, *iter);
}

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

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

Traits编程技法

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

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

 template<typename I>
inline void func(I iter)
{
func_impl(iter, *iter);
} template<typename T>
struct MyIter
{
typedef T value_type;
T* ptr;
MyIter(T* p = ) :ptr(p){}
T& operator*()const { return *ptr; }
//...
}; template<typename T>
typename I::value_type func(I ite)
{
return *ite;
} MyIter<int> ite(new int());
cout << func(ite); //输出 8

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

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

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

 template<typename T>
class C
{...};

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

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

  至此,对于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:

 template<typename I, typename T>
typename itertor_traits<I>::difference_type //函数返回值,是个类型
count(I first, I last, const T& value) {
typename itertor_traits<I>::difference_type n = ;
for ( ; first != last; ++first)
{
if (*first == value)
++n;
}
return n;
}

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

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

 template<typename I>
struct iterator_traits
{
...
typedef typename I::difference_type difference_type;
};
//针对原生指针而设计的偏特化版本
template<typename T>
struct iterator_traits<T*>
{
...
typedef ptrdiff_t difference_type;
};
//针对原生的pointer-to-const而设计的偏特化版本
struct iterator_traits<const T*>
{
...
typedef ptrdiff_t difference_type;
};

迭代器相应类型之三: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中出现过:

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

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

 template<typename I>
struct iterator_traits
{
...
typedef typename I::pointer pointer;
typedef typename I::reference reference;
};
template<typename T>
struct iterator_traits<T*>
{
...
typedef T* pointer;
typedef T& reference;
};
struct iterator_traits<const T*>
{
...
typedef const T* pointer;
typedef const T& reference;
};

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

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

  1. STL源码剖析——iterators与trait编程&num;4 iterator源码

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

  2. STL源码剖析——iterators与trait编程&num;3 iterator&lowbar;category

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

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

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

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

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

  5. STL源码剖析——Iterators与Traits编程&num;5 &lowbar;&lowbar;type&lowbar;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&quot&semi;源码&quot&semi;剖析-重点知识总结

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

随机推荐

  1. PDA手持终端扫描条码开单打印一体 结合后台电脑系统 数据同步交互解决方案

    PDA通过扫描商品条码移动开单,实现便携式办公,伴随式销售,PDA能通过WIFI无线局域网.GPRS互联网直接与主机连接,让公司业务人员能随时随地了解公司产品信息,直接扫描商品条码,进行开单.入库.库 ...

  2. 解决OneNote的无法同步的问题

    微软的OneNote一直是我比较喜欢的笔记软件,一些复杂的笔记用它来存储比Evernote要强大很多,然而这几天发现一直无法同步,登陆https://onedrive.live.com/看了一下,发现 ...

  3. Gvim使用心得--设置篇&lbrack;转&rsqb;

    1.设置自己喜欢的字体? 点“编辑”--“选择字体”, 然后在字体列表中选择一个你喜欢的字体和字号,然后确认. 如果想每次都使用这个这个字体 需要加到启动文件中 比如我的 set guifont=Co ...

  4. 安装64位Oracle 10g超详细教程

    安装64位Oracle 10g超详细教程 1. 安装准备阶段 1.1 安装Oracle环境 经过上一篇博文的过程,已经完成了对Linux系统的安装,本例使用X-Manager来实现与Linux系统的连 ...

  5. hadoop高可靠性HA集群

    概述 简单hdfs高可用架构图 在hadoop2.x中通常由两个NameNode组成,一个处于active状态,另一个处于standby状态.Active NameNode对外提供服务,而Standb ...

  6. &period;NET项目从CI到CD-Jenkins&lowbar;Pipeline的应用

    一.罗里吧嗦 最近迁移了服务器,顺道完善下服役了一两年的Jenkins服务,主要是把Slave搭建起来,还有等等.本文只是我对Jenkins Pipeline的一些自己的理解与应用,欢迎指出错误,欢迎 ...

  7. KindEditor富文本编辑器, 从客户端中检测到有潜在危险的 Request&period;Form 值

    在用富文本编辑器时经常会遇到的问题是asp.net报的”检测到有潜在危险的 Request.Form 值“一般的解法是在aspx页面   page  标签中加上 validaterequest='fa ...

  8. 关于Setup Factory 9的一些使用方法

    之前使用的VS自带的InstallShield2015LimitedEdition 打包工具,但是不太灵活,打包长得也难看:后来使用Setup Factory 9 打包winform应用程序,用起来轻 ...

  9. &lbrack;转载&rsqb; python必碰到的问题---encode与decode,中文乱码

    阅读来源: 字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicod ...

  10. PHP 中 var&lowbar;export、print&lowbar;r、var&lowbar;dump 调试中的区别

    1.output basic type 代码 $n = "test"; var_export($n); print_r($n); var_dump($n); echo '----- ...