STL源码剖析阅读——迭代器Iterators


前言

迭代器(iterators)是一种抽象的设计模式,现实程序语言中没有与之对应的实物。迭代器模式定义如下:提供一种方法,使之能够依序遍历某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表达方式。

一、迭代器设计思维——STL关键所在

STL的中心思想在于使容器和算法分开,彼此独立设计,最后再利用胶合剂将两者进行结合。容器和算法分别可以利用class template和function template可以实现,如何设计适合两者的胶合剂是一个难点。
在这里插入图片描述
只要给予不同的迭代器,find()便能对不同的容器进行查找操作!
在这里插入图片描述

二、迭代器是一种智能指针(smart point)

迭代器是一种行为类似指针的对象,指针的各种行为中最为常见也是最为重要的便是解引用(dereference)和成员访问(member access)。因此,迭代器最重要的工作就是对operator*和operator->进行重载。C++中提供了智能指针也解决原始指针(native pointer)内存泄漏问题,书中以auto_ptr为例子进行说明。
在这里插入图片描述
在这里插入图片描述

案例——为list设计一个迭代器

书上的案例不太完整,本文进行了补充。
list节点

template <class T>
class ListItem
{
public:
    ListItem(T value):_value(value)
    {
        this->_next = nullptr;
    }
    T value() const {return _value;}
    ListItem* next()  {return _next;}
    T operator*()
    {
        return _value;
    }
    bool operator==(const ListItem<T> &item)
    {
        return this->_value == item.value();
    }
//private:
    T _value;
    ListItem *_next;

};

list

template<class T>
class MyList
{
public:
    MyList()
    {
        _front = nullptr;
        _end = nullptr;
        _size = 0;
    }
    void insert_front(T value);
    void insert_end(T value);
    void display(ostream &os = cout);
    ListItem<T>* front()
    {
        return _front;
    }
    ListItem<T>* end()
    {
        return _end;
    }
private:
    ListItem<T> *_front;
    ListItem<T> *_end;
    long _size;
};

template<class T>
void MyList<T>::insert_front(T value)
{
    _size++;
    ListItem<T> * temp = new ListItem<T>(value);
    if(this->_front == nullptr)
    {
        this->_front = temp;
        return;
    }
    temp->_next = this->_front;
    this->_front = temp;
}

template<class T>
void MyList<T>::insert_end(T value)
{
    _size++;
    ListItem<T> * temp = new ListItem<T>(value);
    ListItem<T> * it = this->_front;
    while(it->next() != nullptr)
    {
        it = it->next();
    }
    it->_next = temp;
}

template<class T>
void MyList<T>::display(ostream &os)
{
    auto it = this->front();
    while(_size)
    {
        cout << it->value()<< " ";
        _size--;
        it = it->next();
    }
    cout << endl;
}

如何将MyList套用之前所说的find()?这就需要我们为之设计一个行为类似指针的外衣,也就是一个迭代器。当我们解引用这一迭代器时,传回的应该是一个ListItem对象;当我们递增该迭代器时,它应该指向下一个ListItem对象。为了让该迭代器适用于任何形态的节点,而不只限于ListItem,我们可以将它设计为一个class template。
迭代器

template<class Item>
struct ListIter
{
    Item *ptr; // 保持与容器之间的一个联系
    ListIter()=default;
    ListIter(Item *p):ptr(p){}
    
    Item& operator*(){return *ptr;}
    Item* operator->(){return ptr;}
    
    ListIter& operator++() // 前置++
    {
        ptr = ptr->next();
        return *this;
    }
    ListIter operator++(int)
    {
        ListIter temp = *this;
        ++*this;
        return temp;
    }
    bool operator==(const ListIter& i)
    {
        return this->ptr == i.ptr;
    }
    bool operator!=(const ListIter& i)
    {
        return this->ptr != i.ptr;
    }
};

main

int main()
{
    MyList<int> list;
    for(int i=0; i<5; ++i)
    {
        list.insert_front(i);
        list.insert_end(i+2);
    }
    list.display();
    ListIter<ListItem<int> > begin(list.front());
    cout << begin.ptr->value() << endl;
    ListIter<ListItem<int> > end(list.end());
    ListIter<ListItem<int> > iter;
    iter = find(begin, end, 2);
    if(iter == end)
    {
        cout << "false" << endl;
    }
    else
    {
        cout << "true" << endl;
    }
}

注意,find()函数内部采用的是*iter== value来判断元素值是否吻合,故在list节点类中对operator==进行了重载,当然也可以像书中使用全局函数进行重载。

bool operator==(const ListItem<T> &item)
{
    return this->_value == item.value();
}

在这里插入图片描述
对于迭代器和调用者而言,list中的节点内应该是完全隐藏的。换句话说,我们应该指暴露容器,关于容器的内部实现应该是完全隐藏的。

三、迭代器相应型别(associated types)

这里的型别指的就是type,大陆惯用叫法是类别,书中是型别。
在这里插入图片描述
在这里插入图片描述
解决办法是:利用function template的参数推导(argument deducation)机制。
在这里插入图片描述
迭代器相应型别不知道“迭代器所指对象的型别”一种而已。根据经验,最常用的相应型别有五种,然而并非任何情况下任何一种都可利用上述的template参数推导机制来取得。我们需要更全面的解法。

四、Traits编程技法——STL源码门钥

在这里插入图片描述
在这里插入图片描述
上述做法存在的问题在于并不是所有的迭代器都是class type! 原生指针就不是!如果不是class type,就无法为它定义内嵌型别。但STL(以及整个泛型思维)绝对必须接受原生指针作为迭代器,所以上面是不够的!有没有办法可以让上述的一般化概念针对特定情况(例如针对原生指针)做特殊化处理呢?是的,template partial specialization可以做到。
模版特化和偏特化
模板偏特化的大致的意义是:如果class template拥有一个以上的template参数,我们可以针对其中某个(或数个,但非全部)template参数进行特化工作。换句话说,我们可以在泛化设计中提供一个特化版本(也就是将泛化版本中的某些template参数赋予明确的指定)

在这里插入图片描述

//原来的func:
template <class I>
typename I::value func(I ite)
{
	return *ite;
}
//目前可以改写成这样
template <class I> typename iterator_traits<I>::value_type func(I ite)
{
	return *ite;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 迭代器相应型别之一:value type
    在这里插入图片描述
  • 迭代器相应型别之二:difference type
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 迭代器相应型别之三:reference type
    在这里插入图片描述
  • 迭代器相应型别之四:pointer type
    在这里插入图片描述
    在这里插入图片描述
  • 迭代器相应型别之五:iterator_category
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    当程序调用advance()时,应该调用那一份函数了?如果选择advance_II(),对Random Access Iterator而言极度缺乏效率,原本O(1)的操作竟成为O(N)。如果选择advance_RAI(),则它无法接受InputIterator。我们需要将三者合一,下面是一种做法:
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n)
{
	if(is_random_access_iterator(i))  // 此函数待设计
		advance_RAI(i, n);
	else if(is_bidirectional_iterator(i)) // 此函数待设计
		advance_BI(i, n);
	else
		advance_II(i, n);
}

但是上面的做法是在执行期才能确定使用哪一个版本,会影响程序效率。最好的做法是在编译期就选择正确的版本。重载函数机制可以达成这个目标。 前面三个advance_xx()都有两个函数参数,型别都未定(因为都是template参数)。为了令其同名,形成重载函数,我们必须加上一个型别已确定的函数参数,使函数重载机制得以有效运作起来。
设计考虑如下:如果traits有能力萃取出迭代器的种类,我们便可以利用这个“迭代器类型”相应型别作为advanced()的第三个参数。这个相应型别一定必须是个class type,不能只是数值号码类的东西,因为编译器需依赖它(一个型别)来进行重载决议(overload resoulution)。
下面定义五个classes,代表物种迭代器类型:

// 五个作为标记用的型别(tag types)
struct input_iterator_tag{};
struct ouput_iterator_tar{};
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只作为标记用,所以不需要任何成员。至于为什么运用继承机制,稍后再解释。现在重新设计__advance()(由于只在内部使用,所以函数名称加上特定的前导符),并加上第三个参数,使它们形成重载:

template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag)
{
    //单向,逐一前进
    while(n--) ++i;
}

//这是一个单纯的传递调用函数(trivial forwarding function)
template <class ForwardIterator, class Distance>
inline void __advance(ForwardIterator& i, Distance n, forward_iterator_tag)
{
    // 单纯地进行传递调用(forwarding)
    advance(i, n, input_iterator_tag());
}

template <class BidiectionalIterator, class Distance>
inline void __advance(BidiectionalIterator& i, Distance n, bidirectional_iterator_tag)
{
    // 双向,逐一前行
    if(n >= 0)
        while(n--) ++i;
    else
        while(n++) --i;
}

template <class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag)
{
    // 双向,跳跃前进
    i += n;
}

注意:上述__advance函数模版中的最有一个参数都只声明型别,并未指明参数名称,因为它纯粹只是用来激活重载机制,函数之中根本不适用该参数。
在这里插入图片描述
注释3的解释如下:
在这里插入图片描述
在这里插入图片描述
注意:任何一个迭代器,其类型永远应该落在“该迭代器所隶属之各种类型中,最强化的那个”。例如,int* 既是Random Access Iterator,又是Bidirectional Iterator,同时也是Forward Iterator,而且也是Input Iterator,那么,其类型应该归属为random_access_iterator_tag。
在这里插入图片描述

五、std::iterator的保证

为了符合规范,任何迭代器都应该提供五个内嵌相应型别,以利于traits萃取,否则便是自别于整个STL架构,可能无法与其他STL组件顺利搭配。STL提供了一个iterators class如下,如果每个新设计的迭代器都继承自它,就可保证符合STL所需之规范。

template <class Category, 
          class T,
          class Distance = ptrdiff_t,
          class Pointer = T*,
          class Reference = T&>
struct iterator
{
    typedef Category iterator_category;
    typedef T value_type;
    typedef Distance difference_type;
    typedef Pointer pointer;
    typedef Reference reference;
};

iterator class不含任何成员,纯粹只是型别定义,所以继承它并不会招致任何额外负担。由于后三个参数皆有默认值,故新的迭代器只需提供前两个参数即可。
那么前面的ListIter就应该改写成如下正式格式:

template <class Item>
struct ListIter : public std::iterator<std::forward_iterator_tag, Item>
{
    ...
};

小总结

设计适当的相应型别(associated types),是迭代器的责任。设计适当的迭代器,则是容器的责任。只有容器本身,才知道该设计出怎样的迭代器来遍历自己,并执行迭代器该有的各种行为(前进、后退、取值、取用成员…)。至于算法,完全可以独立于容器和迭代器之外自行发展,只要设计时以迭代器为对外接口就行。

六、iterator源代码完整重列

以下为SGI STL<stl_iterator.h>头文件内本章相关的程序代码。该头文件还有其他内容,是关于iostream iterators、inserter iterators以及reverse iterators的实现。


// 节录自 SGI STL <stl_iterator.h>
// 五种迭代器类型
struct input_iterator_tag{};
struct ouptut_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{};

// 为了避免写代码时遗漏,自行开发的迭代器最好继承自下面这个 std::iterator
template <class Category, class T, class Distance=ptrdiff_t,
          class Pointer=T*, class Reference = T&>
struct iterator
{
    typedef Category iterator_category;
    typedef T value_type;
    typedef Distance difference_type;
    typedef Pointer pointer;
    typedef Reference reference;
};

// 针对原生指针(native pointer)而设计的traits偏特化版
template <class T>
struct iterator_traits<T*>
{
    typedef random_access_iterator_tag iterator_category;
    typedef T value_type;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef T& reference;
};

// 针对原生指针pointer-to-const而设计的traits偏特化版
template <class T>
struct iterator_traits<const T*>
{
    typedef random_access_iterator_tag iterator_category;
    typedef T value_type;
    typedef ptrdiff_t difference_type;
    typedef const T* pointer;
    typedef const T& reference;
};

// 这个函数可以很方便地决定某个迭代器的类型(category)
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category 
iterator_category(const Iterator&)
{
    typedef typename iterator_traits<Iterator>::iterator_category category;
    return category();
}

// 这个函数可以很方便地决定某个迭代器的 distance type
template <class Iterator>
inline typename iterator_traits<Iterator>::difference_type* 
distance_type(const Iterator&)
{
    return static_cast<typename iterator_traits<Iterator>::difference_type*>(0);
}

// 这个函数可以很方便地决定某个迭代器 value type 
template <class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&)
{
    return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}

// 以下是整数distance函数
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
__distance(InputIterator first, InputIterator last, input_iterator_tag)
{
    iterator_traits<InputIterator>::difference_type n=0;
    while(first != last)
    {
        ++first;
        ++n;
    }
    return n;
}

template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
__distance(RandomAccessIterator first, RandomAccessIterator last, 
           random_access_iterator_tag)
{
    return last-first;
}

template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last)
{
    typedef typename iterator_traits<InputIterator>::iterator_category category;
    return __distance(first, last, category());
}

// 以下是整数advance函数
template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag)
{
    while(n--) ++i;
}

template <class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag)
{
    if(n >= 0)
        while(n--) ++i;
    else
        while(n++) --i;
}

template <class RamdomAccessIterator, class Distance>
inline void __advance(RamdomAccessIterator& i, Distance n, random_access_iterator_tag)
{
    i+=n;
}

template <class InputIterator, class Distance>
inline void advance(InputIterator &i, Distance n)
{
    __advance(i, n, iterator_category(i));
}

七、SGI STL的私房菜:__type_traits

traits编程技法很棒,适度弥补了C++语言本身的不足。STL只对迭代器加以规范,制定出iterator_traits这样的东西。SGI把这种技法进一步扩大到迭代器以外的世界,于是有了所谓的__type_traits。双底线前缀词意指这是SCI STL内部所用的东西,不在STL标准规范之内。
iterator_traits负责萃取迭代器的特性,__type_traits则负责萃取型别(type)的特性。此处关注的型别特性是指:这个型别是否具备non-trivial defalt ctor?是否具备non-trivial copy ctor?是否具备non-trivial assignment operator?是否具备non-trivial dtor?如果答案是否定的,我们在对这个型别进行构造、析构、拷贝、赋值等操作时,就可以采用最有效率的措施(例如根本不调用身居高位,不谋实事的那些constructor,destructor),而采用内存直接处理操作如malloc()、memcpy()等等,这对于大规模而操作频繁的容器,有着显著的效果提升。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
C++11支持的type traits可参考这个

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值