STL源码学习之迭代器

迭代器的设计思维 - STL的关键所在

不论是泛型编程或是STL的实际应用,迭代器都扮演着重要角色。STL的中心思想在于:将数据容器(containers)和算法(algorithms)分开,彼此独立设计,最后再以一贴胶着剂将他们撮合在一起。

template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& value) {
    // 修改*first!=value
    while (first!=last && *first!=value)
    // while (first!=last && (*first).value()!=value)
        ++first;
    return first;
}

迭代器是一种 smart point

迭代器是一种类似指针的对象,而指针里各种行为最常见也是最重要的便是内容提领(dereference)和成员访问(member access),因此,迭代器最重要的编程工作就是对 operator*operator-> 进行重载工作。

template <class Item>
struct ListIter {
    Item *ptr; // 保持与容器的联系

    // default ctor
    ListIter(Item* p =0) : ptr(p) {}

    // 解引用 dereference
    Item& operator*() const {
        return *ptr;
    }

    // member access
    Item* operator->() const {
        return ptr;
    }

    // prefix increment, 返回对象
    ListIter& operator++() {
        ptr = ptr->next();
        return *this;
    }

    // postfix increment, 返回值(新对象)
    // int为占位符,提示编译器这是后自增
    ListIter operator++(int) {
        ListIter temp = *this;
        ++*this; // 调用前面的前自增
        return temp;
    }

    bool operator==(const ListIter& i) const {
        return ptr == i.ptr;
    }

    bool operator!=(const ListIter& i) const {
        return ptr != i.ptr;
    }
};

template <typename T>
class List {
// 定义public以供访问
public:
    void insert_front(T value);
    void insert_end(T value);
    void display(std::ostream &os = std::cout) const;
    ListItem<T> * front() {
        return _front;
    }
    List();

private:
    ListItem<T> *_end;
    ListItem<T> *_front;
    long _size;
};

//为了完成一个针对 List而设计的迭代器,我们无可避免曝露了太多 List细节:
// 在 main()为了制作 begin和 end两个迭代器,我们曝露了 ListItem;
// 在 ListIter class 为了达成 operator++的目的,我们曝露了 ListItem的操作函式 next()。
// 如果不是为了迭代器,ListItem 原本应该完全隐藏起来不曝光的。
// 换句话说,要设计出 ListIter,首先必须对 List 的细节有非常丰富的了解。
// 既然这无可避免,干脆就把迭代器的开发工作交给 List的设计者好了,
// 如此一来所有细节反而得以封装起来不被使用者看到。
// 这正是为什么每一种  STL  容器都提供有专属迭代器的缘故。

// 如果不写全局比较函数,则需要更改find函数判定
template <typename T> bool operator!=(const ListItem<T>& item, T n) { 
    return item.value() != n; 
} 

迭代器相应型别

迭代器所指之物就是相应型别的一种
在这里插入图片描述

Trait编程技法 - STL源代码密钥

迭代器所指对象的型别,称为该迭代器的value type。上述的参数型别推导技巧虽可用于value type,却非全面可用;万一 vaue type 必须用于函数的传回值,就束手无策了。
我们需要其他方法,声明内嵌型别是个好主意

template <class T>
struct MyIter {
	typedef T value_type;  	// 内嵌型别声明
	T* ptr;
	MyIter(T* p = 0) : ptr(p) { }
	T& operator*() const {return *ptr;}
	// ...
};
/*
	func 的返回值必须加上 typename,因为 T 是一个 template 参数
	typename 告诉编译器这是一个型别,才能通过编译
*/
template <class I>
typename I::value_type 		// 这一行是 func 的返回值型别
func (I ite)
{ return *ite; }

Myiter<int> ite(new int(8));
cout << func(ite);

在STL实现中,traits编程技术得到大量的运用,它利用了“内嵌类型”的编程技巧与C++的template参数推导功能,弥补了C++类型识别方面的不足。通过traits,算法可以原汁原味的将迭代器的属性萃取出来,帮助算法正确高效的运行。
在这里插入图片描述

tempalte<typename I>
struct iterator_traits
{
    typedef typename I::iterator_category iterator_category;
    typedef typename I::value_type value_type;
    typedef typename I::difference_type difference_type;
    typedef typename I::pointer pointer;
    typedef typename I::reference reference;
};

1、 迭代器相应型别之一:value type

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

2、 迭代器相应型别之二:difference type

两个迭代器之间的距离,因此它可以表示一个容器的最大容量;如果一个泛型算法提供计数功能,如count(),其返回值就必须使用迭代器的difference type;

定义difference_type:

// 泛化版本
template<class T>
struct iterator_traits{
	...
	typedef typename I::difference_type difference_type;
}

// 针对原生指针的偏特化版本(非const)
template<class T>
struct iterator_traits<T*>{
	...
	typedef ptrdiff_t difference_type;
}

// 针对原生指针的偏特化版本(const)
template<class T>
struct iterator_traits<const T*>{
	...
	typedef ptrdiff_t difference_type;
}

3 迭代器相应型别之三:reference type

根据迭代器所指对象能否改变,迭代器被分为两类:

不允许改变:constant iterators,如const int* pic;
允许改变:mutable iterators,如int* pi。
左值 如 i = 5 + 1,i 在运算符的左边,称为左值。

在C++中,如果函数要传回左值,都是以by reference的方式进行的:

如果p是一个对象值能改变的迭代器,设它的value type是T,那么p的类型不能是T,而应该是T&;
如果p是一个对象值不能改变的迭代器,设它的value type是T,那么
p的类型不能是const T,而应该是const T&。

4 迭代器相应型别之四:pointer type

我们可以传回一个pointer,指向迭代器所指之物。

5 迭代器相应型别之五:iterator_category

根据移动特性与实施操作,迭代器被分为五类:

Input Iterator:这种迭代器所指的对象,不允许外界改变,即read only;
Output Iterator:write only;
Forward Iterator:允许写入型算法在此种迭代器所形成的区间上进行读写操作;
Bidirectional Iterator:可双向移动,即可以正向遍历,也可以反向遍历;
Random Access Iterator:前四种迭代器只支持部分指针算术能力(前三种支持++,第四种在此基础上支持–),但是第五种则涵盖所有指针算术能力,如p+n、p-n、p[n]、p1-p2、p1<p2。

在编写代码的时候最好能够在编译期就能决定使用哪个版本,如果在运行期使用 if else 来判断,会影响效率,因此推荐使用重载函数来解决(不同的迭代器类型会有不同的计算法方式,以达到效率的最大化)。

STL算法命名规则: 以算法所能接受之最低阶迭代器类型,来为其迭代器型别参数命名(即最低层次,最泛化的那一个)。

std::iterator 的保证

STL提供了一个超类iterator,如果每个设计的迭代器类都继承自它,就可以保证符合STL的规范,iterators class不含任何成员函数,纯粹只是型别定义(因为如果设计的迭代器不含五个内嵌相应型别,traits就无法萃取它,它也会独立于整个STL之外)。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;
}

因此,声明一个迭代器类只需要继承它,然后指定 Category 和 T 的值即可:

template<class Item>
struct ListIter : public std::iterator<std::forward_iterator_tag,Item>

总结:

  1. 迭代器的责任:设计相应的型别(包含traits(泛化版、针对原生指针的偏特化版、针对const的偏特化版))

  2. 容器的责任:设计适当的迭代器,只有容器本身才知道如何设计一个迭代器来遍历自己,并执行迭代器该有的各种行为(如前进、后退、取值、取用成员等等)。

  3. 算法:则完全独立于容器和迭代器之外自行发展,只要设计时以迭代器为对外接口即可。

SGI STL 的私房菜:__type_traits

type_traits: 负责萃取迭代器的特性
__type_traits: 负责萃取型别的特性,即判断这个型别是否具有 non-trivial 默认的构造函数、是否具备 non-trivial 拷贝构造函数、 non-trivial 赋值操作符、 non-trivial dtor(析构),如果不含有这些函数,那么在对这个类型进行构造、析构、拷贝、赋值等操作时,就可以采用最有效率的、直接针对内存的操作,如malloc()、memcpy()等等,从而获得最高的效率。
根据 iterator_traits 的实现方法,我们可以类比实现出 __type_traits (用来返回类型的特性):

   typedef __type_traits::has_trivial_default_constructor; // 构造函数
   typedef __type_traits::has_trivial_copy_constructor; // 拷贝构造函数
   typedef __type_traits::has_trivial_assignment_operator; // 赋值运算符
   typedef __type_traits::has_trivial_destructor; // 析构
   typedef __type_traits::is_POD_type; // Plain Old Data

我们希望上面能够返回带true或者false的对象,因为我们希望根据该对象进行参数推导,因此我们应该传回(即一个类的对象):

struct __true_type {}; // 表示可以用最快的方法(即针对内存的方式)来进行拷贝或辅助操作
struct __false_type {};

__type_traits具体的一个实现:

template<class type>
struct __type_traits{
	typedef __true_type this_dummy_member_must_be_first; // 不要删除该行,避免冲突
	typedef __false_type has_trivial_default_constructor; // 构造函数
   	typedef __false_type has_trivial_copy_constructor; // 拷贝构造函数
   	typedef __false_type has_trivial_assignment_operator; // 赋值运算符
   	typedef __false_type has_trivial_destructor; // 析构
   	typedef __false_type is_POD_type; // Plain Old Data
}

保守起见,默认的所有对象都是 __false_type 的,即不能直接使用内存进行操作,然后通过定义特化版本的 __type_traits ,可以解决这个问题,如:

// 针对基本数据类型的偏特化版本
// __STL_TEMPLATE_NULL 是类模板的显示(excplicit)特化(specialization)
__STL_TEMPLATE_NULL struct __type_traits<char/int/double/...>{
	typedef __true_type  has_trivial_default_constructor; // 构造函数
   	typedef __true_type has_trivial_copy_constructor; // 拷贝构造函数
   	typedef __true_type has_trivial_assignment_operator; // 赋值运算符
   	typedef __true_type has_trivial_destructor; // 析构
   	typedef __true_type is_POD_type; // Plain Old Data
}
// 针对原生指针的偏特化版本
template<class T>
struct __type_traits<T*>{
	typedef __true_type  has_trivial_default_constructor; // 构造函数
   	typedef __true_type has_trivial_copy_constructor; // 拷贝构造函数
   	typedef __true_type has_trivial_assignment_operator; // 赋值运算符
   	typedef __true_type has_trivial_destructor; // 析构
   	typedef __true_type is_POD_type; // Plain Old Data
}

什么时候一个class该有自己的non-trivial-xxx呢:

如果class内含指针成员,并且对它进行内存动态配置,那么这个class就需要实现出直接的non-trivial-xxx。
写一个迭代器类要做的事情: 指定迭代器五大基本类型(或继承iterator基类,就只需要指定前两个类型)、定义好别名(STL编码规范)、构造函数、重载运算符等等

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值