STL源码分析《4》----Traits技术
在 STL 源码中,到处可见 Traits 的身影,其实 Traits 不是一种语法,更确切地说是一种技术。
STL库中,有一个函数叫做 advance, 用来将某个迭代器(具有指针行为的一种 class)移动
某个给定的距离。声明如下:
template <typename IterT, typename DistT> // 将迭代器向前移动 d 单位
void advance(IterT& iter, DistT d); // 如果 d < 0, 则向后移动
通常来说, iter += d 即可。但是其实不是。因为只有随机迭代器才支持算数运算。
而其他的迭代器种类,advance 只能一步步 ++ iter,走 d 步。
STL 库共有 5 种迭代器:
1. Input 迭代器
2. Output 迭代器
3. forward 迭代器
4. Bidirectional 迭代器
5. random 迭代器
对于这五种迭代器,STL 定义了五个专属的卷标结构(tag struct)加以标识。
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 { };
他们的用途我在稍微会讲到。
现在回到 advance 函数。为了让 advance 满足不同迭代器的需要, 并且效率达到最高,
我们期望它以如下的方式定义:
template <typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if ( iter is a random access iterator) {
iter += d; // 针对 random_access 迭代器采用迭代器算数运算,复杂度是 O(1)
}
else {
if (d > 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
难点在于如何判断 iter 是否是 random_access 迭代器这种类型?? 这里就是 traits 所做之事。
traits 技术让你能够 萃取 出迭代器的某些性质: 比如,迭代器类型,所指向元素的类型等等。
下面开始详细讲解 traits:
可以说它主要由两部分组成:
1. stl 中定义的 iterator_traits 的模板类:
template <typename IterT>
struct iterator_traits;
这个模板类里面包含了许多的 typedef, 其中有一个 typedef 名为 iterator_category,就是用来确认迭代器分类。
2. 要求用户自定义的迭代器类型中,必须定义一个 iterator_category 的 typedef.
举个例子:加入下面是 list 迭代器的定义,注意 list 是有双链表实现的,所以迭代器应该是 bidirectional 迭代器。
class iterator { // for list
public:
typedef bidirectional_iterator_tag iterator_category;
...
};
之前所说的 tag struct 就是用在这里,用来标识随机迭代器这种类型。
而 iterator_traits 里面有一个typedef 如下:
template<typename IterT>
struct iterator_traits {
typedef typename IterT::iterator_category iterator_category;
};
大家明白了吗? 这个其实就是 在迭代器中定义一个public 的 typedef, 告诉外界,我是一个什么迭代器类型。
然后 iterator_traits 仅仅是鹦鹉学舌般地响应 迭代器类的嵌套式 typedef 而已。
截取一种 STL 源码分析上的图:
大家先消化以下。。。。
。。。。。。。。。。。。。。。。。。。。。。。。。
二、
大家可能立刻想到一个问题,这对于 指针行不通。。因为我们不能在指针的内部定义一个 typedef.
怎么办?? 这时候,模板的偏特化技术就派上了用途。
我们可以提供一个 iterator_traits 的针对指针的偏特化版本:
template <typename IterT>
struct iterator_traits <IterT*>
{
typedef random_access_iterator_tag iterator_category; // 一般的指针是随机访问迭代器
....
}
讲了一堆难懂的技术,现在来说说具体是如何用的。。
给出一段 advance 函数的代码,但是会有编译问题并且效率不高。。因此,STL 其实并没有采用。
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (typeid(typename std::iterator_traits<IterT>::iterator_category)
== typeid(std::random_access_iterator_tag) )
....
}
typeid 是 C++ 中用于辨别类型的。。具体参考 C++ primer.
大家知道, IterT 的类型在编译期已经获知,所以 iterator_traits<IterT>::iterator_category
在编译期也可确定。但是 if 语句得到执行期才执行,所以只有到执行期才能判断 iter 是否是随机迭代器。
如何能够在编译期就是判断 iter 是否是随机迭代器呢??(效率更高)
我们需要一个“执行”于编译期的类似 if, else 的语句,
Bingo ! 重载(overloading)
直接上代码。。
template<typename IterT, typename DistT>
void advance(IterT iter, DistT d)
{
doAdvance(iter, d,
std::iterator_traits<IterT>::iterator_category());
// 用 iterator_category 定义一个临时对象,作为参数
} // for random_access iterator
template<typename IterT, typename DistT>
void doAdvance(IterT iter, DistT d,
std::random_access_iterator_tag)
{
iter += d;
} // overload
// for bidirectional iterators
template<typename IterT, typename DistT>
void doAdvance(IterT iter, DistT d,
std::bidirectional_iterator_tag)
{
if(d >= 0){
while(d--) ++iter;
}
else{
while(d++) --iter;
}
} // overload
// for input iterators
template<typename IterT, typename DistT>
void doAdvance(IterT iter, DistT d,
std::input_iterator_tag)
{
if(d < 0){
throw std::out_of_range("Negative distance");
}
while(d--) ++iter;
}
通过定义不同迭代器的 doAdvance 函数的重载版本,
参数唯一不同的是 之前说用来标识迭代器的 tag_struct:
input_iterator_tag
bidirectional_iterator_tag
random_access_iterator_tag
有没有觉得这种技术很酷??
利用重载函数完美地将执行期判断类型是否是某个迭代器,提前到了编译期,
极大地提高了速度。
参考书籍:
Stanley, B. Lippman, C++ Primer 第四版,
Scott, Meyers, Effective C++,
侯捷, STL 源码分析
STL源码分析《4》----Traits技术的更多相关文章
- STL源码分析读书笔记--第二章--空间配置器(allocator)
声明:侯捷先生的STL源码剖析第二章个人感觉讲得蛮乱的,而且跟第三章有关,建议看完第三章再看第二章,网上有人上传了一篇读书笔记,觉得这个读书笔记的内容和编排还不错,我的这篇总结基本就延续了该读书笔记的 ...
- STL源码分析《3》----辅助空间不足时,如何进行归并排序
两个连在一起的序列 [first, middle) 和 [middle, last) 都已经排序, 归并排序最核心的算法就是 将 [first, middle) 和 [middle, last) 在 ...
- STL 源码分析《1》---- list 归并排序的 迭代版本, 神奇的 STL list sort
最近在看 侯捷的 STL源码分析,发现了以下的这个list 排序算法,乍眼看去,实在难以看出它是归并排序. 平常大家写归并排序,通常写的是 递归版本..为了效率的考虑,STL库 给出了如下的 归并排序 ...
- STL源码--iterator和traits编程技法
第一部分 iterator学习 STL iterators定义: 提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表达方式. 任何iteartor都应该提供5 ...
- STL 源码分析《2》----nth_element() 使用与源码分析
Select 问题: 在一个无序的数组中 找到第 n 大的元素. 思路 1: 排序,O(NlgN) 思路 2: 利用快排的 RandomizedPartition(), 平均复杂度是 O(N) 思路 ...
- stl源码分析之allocator
allocator封装了stl标准程序库的内存管理系统,标准库的string,容器,算法和部分iostream都是通过allocator分配和释放内存的.标准库的组件有一个参数指定使用的allocat ...
- STL源码分析与实现-stl_list容器
1. stl_list 介绍 今天我们来总结一下stl_List, 通过之前介绍单链表的文章,其实对链表的基本操作已经十分熟悉了,那对于stl_list,无非就是链表结构不一样,至于其中的增删改查的细 ...
- STL 源码分析六大组件-allocator
1. allocator 基本介绍 分配器(allocator))是C ++标准库的一个组件, 主要用来处理所有给定容器(vector,list,map等)内存的分配和释放.C ++标准库提供了默认使 ...
- STL源码分析之迭代器
前言 迭代器是将算法和容器两个独立的泛型进行调和的一个接口. 使我们不需要关系中间的转化是怎么样的就都能直接使用迭代器进行数据访问. 而迭代器最重要的就是对operator *和operator-&g ...
随机推荐
- jmeter笔记8
JMETER接口性能测试方案 JMETER简介 JMeter可以用于测试静态或者动态资源的性能(文件.Servlets.Perl脚本.java对象.数据库和查询.ftp服务器或者其 ...
- NodeJS 各websocket框架性能分析
For a current project at WhoScored, I needed to learn JavaScript, Node.js and WebSocket channel, aft ...
- PHP中的CURL函数库
PHP中的CURL函数库(Client URL Library Function) curl_close — 关闭一个curl会话curl_copy_handle — 拷贝一个curl连接资源的所有内 ...
- C++继承与派生(原理归纳)
1. C++继承与java不同,java遵循单继承,但java的接口为其不足做了很好的弥补了. C++则是灵活的多,为多继承.即一个C++类可以同时继承N个类的属性. 2. 对于继承方式 : 有三 ...
- javaWeb学习之运用myeclipse结合tomcat开发一些简单的jsp和service
servlet是什么? servlet是java服务器端编程.不同于我们之前写的一般的java应用程序,Servlet程序是运行在服务器上的,服务器有很多种.....现在只是用过 tomcat ...
- iOS的常见文件及程序的启动原理
一. iOS中常见文件 (一). Xcode6之前 创建项目,默认可以看见一个存放框架的文件夹 info文件以工程文件名开头,如:第一个项目-Info.plist 项目中默认有一个PCH文件 (二). ...
- html5 <input> placeholder 属性
带有 placeholder 文本的搜索字段: <form action="demo_form.asp" method="get"> <inp ...
- qml android 的一个例子qtHangMan
这个例子有2个好处: 1.解决了黑屏问题 2.演示了应用内购买的问题
- 二分搜索法(转载自vanezkw)
二分查找算法java实现 今天看了一下JDK里面的二分法是实现,觉得有点小问题.二分法的实现有多种今天就给大家分享两种.一种是递归方式的,一种是非递归方式的.先来看看一些基础的东西. 1.算法概念. ...
- bzoj 2561: 最小生成树
#include<cstdio> #include<iostream> #include<cstring> #define M 100009 #define inf ...