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

  假设算法需要声明一个变量,以“迭代器所指对象的类型”为类型,该怎么做?或许我们可能会想到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编程#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. python format 时间格式

    trainData['survey_time'] = pd.to_datetime(trainData['survey_time'],format = '%Y/%m/%d %H:%M') trainD ...

  2. 深度学习面试题26:GoogLeNet(Inception V2)

    目录 第一层卷积换为分离卷积 一些层的卷积核的个数发生了变化 多个小卷积核代替大卷积核 一些最大值池化换为了平均值池化 完整代码 参考资料 第一层卷积换为分离卷积 net = slim.separab ...

  3. 关于如何取消访问https时的提示:“此网站的安全证书存在问题”的解决方法

    问题描述: 症状1:采用IE浏览器通过https/http协议访问网站时,总是提示:“此网站的安全证书存在问题.” 症状2:采用搜狗浏览器打开网页总是看到网页的图片等元素显示不完整,是一个X的小图片. ...

  4. 【Java.Regex】使用正则表达式查找一个Java类中的成员函数

    代码: import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; imp ...

  5. linux系统中如何查看acpi信息?

    答: 进入/sys/firmware/acpi/tables, 然后输入tree命令即可查看acpi信息

  6. linux下phpmailer发送邮件出现SMTP ERROR: Failed to connect to server: (0)错误

    转自:https://www.cnblogs.com/raincowl/p/8875647.html //Create a new PHPMailer instance $mail = new PHP ...

  7. Python3基础 函数 多值参数 元组与字典形式(使用星号对列表与字典进行拆包)

             Python : 3.7.3          OS : Ubuntu 18.04.2 LTS         IDE : pycharm-community-2019.1.3    ...

  8. faster_rcnn mAP

  9. Flutter TextField详解

    原文地址:https://www.jianshu.com/p/54419a143d70 实现TextField说简单也简单,说有坑,坑也不小,下面从易到难介绍一下使用 1.最简单的就是无参数调用构造方 ...

  10. c# list 使用Where()方法过滤数据

    //根据任务id过滤数据 Func<RfidCodeResultDto, bool> expression = c => c.lineTaskId == _lineTaskId; r ...