STL源码之traits编程技法

时间:2022-10-19 22:51:55

摘要

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

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编程&num;2 Traits编程技法

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

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

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

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

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

  7. STL源码分析读书笔记--第三章--迭代器(iterator&rpar;概念与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. FreeMarker的基础语法

    FreeMarker语言 FreeMarker语言概述 FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写. FreeMarker被设计用来生成HTML Web ...

  2. 附录E 安装Kafka

    E.1   安装Kafka E.1.1    下载Kafka Kafka是由LinkedIn设计的一个高吞吐量.分布式.基于发布订阅模式的消息系统,使用Scala编写,它以可水平扩展.可靠性.异步通信 ...

  3. 基于CNN的人脸相似度检测

    人脸相似度检测主要是检测两张图片中人脸的相似度,从而判断这两张图片的对象是不是一个人. 在上一篇文章中,使用CNN提取人脸特征,然后利用提取的特征进行分类.而在人脸相似度检测的工作中,我们也可以利用卷 ...

  4. HDU 5441 离线处理 &plus; 并查集

    题意:给n个节点m条带权值边的无向图.然后q个问题,每次询问点对的数目,点对需要满足的条件是:1)连通:2)其路径的最大权值不能超过询问值. 分析:如果没次询问一次,dfs一次,很可能超时,因此可以用 ...

  5. iOS——关于创建真机调试证书(发布证书,测试(调试)证书,推送调试证书)、iOS开发者账号申请 请用开发者账号去iTunes connect 查看状态

  6. spark connect to Cassandra problem

    Cassandra rowkey is Blob type, cannot select by spark. How?

  7. &lbrack;大山中学dp常练-4 Rounds&rsqb;

    Round#1 2016.9.28 这次晚练十分sb 有点浪费时间 全是dp题 先说过程 3分钟草了第一题之后感觉好像有点水 然后翻了翻题目 看了看第一第四题两颗星 其他三颗星 然后一下子第二题题目太 ...

  8. Java中的List转换成JSON报错(五)

    1.错误描述 Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/beanu ...

  9. 团队开发项目--NABCD模型

    N(need)需求: 鉴于在学校中的大部分爱学习的学生平时都去拍空教室的占有情况,我们发现有的时候太多,导致同学们们拍照会浪费很长的时间,而且空教室的显示不是一下子全出来,有的时候还会出现无法显示的情 ...

  10. 深度学习之PyTorch实战(3)——实战手写数字识别

    上一节,我们已经学会了基于PyTorch深度学习框架高效,快捷的搭建一个神经网络,并对模型进行训练和对参数进行优化的方法,接下来让我们牛刀小试,基于PyTorch框架使用神经网络来解决一个关于手写数字 ...