摘要

主要讨论如何获取迭代器相应型别。使用迭代器时,很可能用到其型别,若需要声明某个迭代器所指对象的型别的变量,该如何解决。方法如下:

function template的参数推导机制

例如:

template<typename I, typename T>
void func_impl(I iter, T t)
{
//T为迭代器所指对象的型别
T tmp;
//.....
} template<typename I>
inline
void func(I iter)
{
//func工作交给func_impl完成
func_impl(iter, *iter);
} int main()
{
int i;
func(&i); return 0;
}

func_impl()是一个 function template,一旦被调用,编译器会自动进行template参数推导,从而导出型别T,无需自己指出型别,解决问题。迭代器相应型别不只是迭代器所指对象的型别一种而已,最常用的相应型别有五种,但并非任何情况都可利用上述template参数推导机制来取得。这就需要其他方法。

Traits编程技法

迭代器所指对象的型别,成为该迭代器的value type,上述模板参数推导并非全面可用,在需要value type作为函数返回值时,就不能解决了。template参数推导的只是参数而已。因此,声明内嵌型别就出现了。

例如:

template<typename T>
struct MyIter
{
typedef T value_type; //内嵌型别声明
T* ptr;
MyIter(T* p = nullptr):ptr(p) { }
T& operator*() const { return *ptr;}
//...
}; template<typename I>
typename I::value_type //函数func()的返回类型,为I类型迭代器中的value_type
func(I ite)
{
return *ite;
}
int main()
{
MyIter<int> ite(new int(8));
cout<<func(ite); //输出8
return 0;
}

func()函数的返回值必须加上关键字typename,用来告诉编译器这时一个模板类型。但并不是所有迭代器都为class type,原生指针就不是,它就不能定义内嵌型别。这时模板偏特化(template partial specialization)就能解决这个问题。

偏特化的意义

如果class template拥有一个以上的template参数,我们可以针对其中某个(或数个,但并非全部)template参数进行特化工作。也就是将泛化版本中的某些template参数给予明确的指定。

如:

template<typename U, typename V, typename T>
class C { };

偏特化不是template参数U、V或T指定某个参数值,而是针对(任何)template参数更进一步的条件限制所设计出来的一个特化版本。看这个例子:

//泛化版本
template<typename T>
class C { }
//偏特化版本
template<typename T>
class C<T*> { }; //解决原生指针的问题

如此便能解决前面的内嵌型别的问题。下面这个class template专门用来萃取迭代器的特性之一 :value_type

template<typename I>
struct Iterator_traits //traits指的是特性
{
typedef typename I::value_type value_type;
};

如果I内定义了自己的value type,通过这个traits萃取出来的value type就是I::value_type,则前面的func(),可改为:

template<typename I>
typename Iterator_traits<I>::value_type
func(I ite)
{
return *ite;
}

现在写出Iterator_traits的一个偏特化版本,这就解决了原生指针的问题,如果写成Iterator_traits<int*>::value_type,便得到int:

template<typename T>
struct Iterator_traits<T*>
{
typedef T value_type;
};

但针对“指向常数对象的指针”,Iterator_traits<const int*>::value_type可萃取到const int,此时若要得到non-const int,就要设计下面这一个偏特化版本:

template<typename T>
struct Iterator_traits<const T*>
{
tpyedef T value_type; //当迭代器为pointer-to-const,得到non-const T
};

现在迭代器MyIter、原生指针int*或const int *,都能通过Iterator_traits取出正确的value type。

所以,每个迭代器应以内嵌型别定义的方式定义出相应型别,以遍traits运作。

迭代器相应型别

value type

value type指迭代器所指对象的型别。

difference type

difference type表示两个迭代器之间的距离,也能表示一个容器的最大容量,对连续的空间而言,头尾的距离就为最大容量。如STL的count(),其返回值就必须使用迭代器的difference type:

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

针对原生指针而写的偏特化版本,以c++内建的ptrdiff_t(定义于cstddef头文件)作为原生指针的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的偏特化版本
template<typename T>
struct iterator_traits<const T*>
{
typedef ptrdiff_t difference_type;
};

现在,我们可以通过写

typename iterator_traits<I>::difference_type

来得到任何迭代器I的difference_type。

reference type

引用类型,传回一个迭代器所指对象的引用

pointer type

传回一个,指向迭代器所指之物的pointer。

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

Item&便是某个迭代器的reference type,而Item*便是其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;
};
//针对pointer-to-const的偏特化版本
template<typename T>
struct iterator_traits<const T*>
{
typedef const T* pointer;
typedef const T& reference;
};

iterator_category

根据移动特性,迭代器被分为五类:

  • Input Iterator: read only
  • Output Iterator: write only
  • Forward Iterator: 允许写入型算法在这种迭代器所形成的区间上进行读写操作
  • Bidirectional Iterator: 可双向移动
  • Random Access Iterator: 涵盖所有指针算数能力,包括p+n,p-n,p[n]等等

它们从上到下,功能依次强化。

下面以advance()函数为例,该函数有两个参数迭代器p,数值n;函数内部将p累进n次。

下面针对三种不同迭代器进行示范:

//InputIterator
template<typename InputIterator, typename Distance>
void advance_II(InputIterator& i, Distance n)
{
//单向逐一前进
while(n--) ++i;
}
//BidirectionalIterator
template<typename BidirectionalIterator, typename Distance>
void advance_BI(BidirectionalIterator& i, Distance n)
{
//双向逐一前进
if(n >= 0)
while(n--) ++i;
else
while(n++) --i;
}
//RandomAccessIterator
template<typename RandomAccessIterator, typename Distance>
void advance_RAI(RandomAccessIteraor& i, Distance n)
{
//双向跳跃前进
i += n;
}
//封装版本
template<typename InputIterator, typename Distance>
void advance(InputIterator& i, Distance n)
{
//分别判断迭代器类型
if(is_random_access_iterator(i))
advance_RAI(i, n);
else if(is_bidirectionl_iterator(i))
advance_BI(i, n);
else
advance_II(i, n);
}

这样在执行期才决定使用哪个版本,会影响效率。最好能在编译器就选择正确版本,重载函数机制就解决了这个问题。

前面3个advance_xx()都有两个template 参数,类型不确定,为了形成重载函数,必须加上一个型别已经确定的函数参数。下面五个classes代表了五种迭代器类型:

//标记用的型别
struct input_iterator_tag { };
struct output_iterator_tag { };
struct forward_iterator_tag: public input_iterator_tag { };
struct bidirectional_iterator_tag: public forward_iterator_tag { };
struct random_access_iterator_tag: public bidirectional_iterator_tag { };

这些classes当做标记用,作为第三个参数,使能达到重载函数的目的:

//InputIterator
template<typename InputIterator, typename Distance>
void _advance(InputIterator& i, Distance n, input_iterator_tag)
{
//单向逐一前进
while(n--) ++i;
}
//ForwardIterator
template<typename ForwardIterator, typenmae Distance>
void _advance(ForwardIterator& i, Distance n, forward_iterator_tag)
{
_advance(i, n, input_iterator_tag())
}
//BidirectionalIterator
template<typename BidirectionalIterator, typename Distance>
void _advance_BI(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag)
{
//双向逐一前进
if(n >= 0)
while(n--) ++i;
else
while(n++) --i;
}
//RandomAccessIterator
template<typename RandomAccessIterator, typename Distance>
void advance_RAI(RandomAccessIteraor& i, Distance n, random_access_iterator_tag)
{
//双向跳跃前进
i += n;
}

每一个_advance()的最后一个参数只声明型别,是为了激活重载函数机制,并不需要参数名称,并且函数中并不使用该参数。下面是对外开放的接口,以调用不同的_advance()。

template<typename InputIterator, typename Distance>
inline void advance(InputIterator& i, Distance n)
{
_advance(i, n, iterator_traits<InputIterator>::iterator_category());
}
//iterator_category()在stl_iterator.h中定义
template<typename I>
inline typename iterator_traites<I>::iterator_category
iterator_category(const I&)
{
typedef typename iterator_traits<I>:iterator_category category;
return category();
}

为满足上述行为,traits中增加相应型别:

template<typename I>
struct iterator_traits
{
//...
typedef typename I::iterator_category iterator_category;
};
//针对原生指针的偏特化版本
template<typename T>
struct iterator_traits<T*>
{
//...
//原生指针为一种RandomAccessIterator
typedef random_access_iterator_tag iterator_category;
};
//针对pointer-to-const的偏特化版本
template<typename T>
struct iterator_traits<const T*>
{
//...
typedef randow_access_iterator_tag iterator_category;
};

任何一个迭代器,它的类型应属于迭代器类型中功能最强大的类型如int* ,既是Random又是Bidirectional,既是Forward又是Input,他应为Random。

总结

设计适当的型别,是迭代器的责任。设计适当的迭代器,是容器的责任。只有容器本身,才知道什么样的迭代器适合自己。至于算法,完全独立于容器和迭代器,只要设计时以迭代器为对外接口就行。

tratis编程技法大量用于STL中,它利用内嵌型别技巧和编译器的template参数推导功能,增强C++的能力。

STL源码之traits编程技法的更多相关文章

  1. STL源码阅读-traits与迭代器

    迭代器模式 提供一种方法,使之能够依序访问容器的各个元素,而又无需暴露容器的内部表述方式 STL设计的中心思想在于将数据容器和算法分离开,容器和算法分开设计,迭代器则是两者之间的胶着剂,一般迭代器的设 ...

  2. STL源码分析-traits

    http://note.youdao.com/noteshare?id=b5fd9f936cd133af3790a8b0e9c35b8a

  3. STL源码--iterator和traits编程技法

    第一部分 iterator学习 STL iterators定义: 提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表达方式. 任何iteartor都应该提供5 ...

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

    在算法中运用迭代器时,很可能用到其相应类型.什么是相应类型?迭代器所指对象的类型便是其中一个.我曾有一个错误的理解,那就是认为相应类型就是迭代器所指对象的类型,其实不然,相应类型是一个大的类别,迭代器 ...

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

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

  6. [转载]《STL源码剖析》阅读笔记之 迭代器及traits编程技法

    本文从三方面总结迭代器   迭代器的思想   迭代器相应型别及traits思想   __type_traits思想 一 迭代器思想 迭代器的主要思想源于迭代器模式,其定义如下:提供一种方法,使之能够依 ...

  7. STL源码分析读书笔记--第三章--迭代器(iterator)概念与traits编程技法

    1.准备知识 typename用法 用法1:等效于模板编程中的class 用法2:用于显式地告诉编译器接下来的名称是类型名,对于这个区分,下面的参考链接中说得好,如果编译器不知道 T::bar 是类型 ...

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

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

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

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

随机推荐

  1. public static void Swap2

    public class Swap2 { public static void Swap2 (IDemo a, IDemo b) { int temp = a.i; a.i = b.i; b.i = ...

  2. jsbridge与通信模型

    三层通信模型: 应用层.解释层.会话层: 通信协议: 通信原语: 报文格式: 网络层: _evaluateJavascript 会话层: #define kQueueHasMessage   @&qu ...

  3. A* 第k短路

    #include <cstdio> #include <algorithm> #include <queue> #include <cstring> # ...

  4. [React] Fix "React Error: Rendered fewer hooks than expected"

    In this lesson we'll see an interesting situation where we're actually calling a function component ...

  5. ES6 let 和const

    一.var 的缺陷:var 只有全局作用域和函数作用域,没有块级作用域,除了在函数内部的变量默认都属于window var foo='hello'; fn=function(){ var foo='w ...

  6. 洛谷 P1097 【统计数字】 题解

    题目背景 警告:数据可能存在加强 题目描述 某次科研调查时得到了nn个自然数,每个数均不超过1500000000(^)( ).已知不相同的数不超过1000010000个,现在需要统计这些自然数各自出现 ...

  7. 【CSGRound2】逐梦者的初心(洛谷11月月赛 II & CSG Round 2 T3)

    题目描述# 给你一个长度为\(n\)的字符串\(S\). 有\(m\)个操作,保证\(m≤n\). 你还有一个字符串\(T\),刚开始为空. 共有两种操作. 第一种操作: 在字符串\(T\)的末尾加上 ...

  8. Python逆向(二)—— pyc文件结构分析

    一.前言 上一节我们知道了pyc文件是python在编译过程中出现的主要中间过程文件.pyc文件是二进制的,可以由python虚拟机直接执行的程序.分析pyc文件的文件结构对于实现python编译与反 ...

  9. JavaScript初探系列(六)——函数

    一.概述 (一)函数的声明 JavaScript 有三种声明函数的方法. (1)function 命令 function命令声明的代码区块,就是一个函数.function命令后面是函数名,函数名后面是 ...

  10. 11 JavaScript Utility Libraries you Should Know in 2019

    11 Useful Javascript utility libraries to speed your development.