从零开始的STL源码剖析(迭代器概念与triats编程技法)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

本章大致介绍了在C++本身限制下,利用一些手段获取STL中对于迭代器所指向对象类型,从而使代码的复用得到增强,当然也使STL容器和算法变得更为融洽


一、迭代器的思维所在

STL的中心思想在于,将数据容器和算法分开,彼此独立设计,最后将他们融合起来。通过class template 和 function template 可分别达成目标。如何将容器与算法进行耦合才是我们设计的重点。
举个例子来理解,容器的作用是将各种类型的数据塞入容器中,容器的内容为一个个item,然而这个item一般是不会暴露给外界的。而算法是对一个个元素进行操作,他并不知道容器内部的耦合方式。所以我们想要一个中间件,他既能够适配容器内部的各种要求,同时能够反映容器元素的相关属性供给外界使用,而这个东西就是迭代器

二、迭代器是一种和智能指针行为模式很像的东西

首先看看作者对古早的auto_ptr的实现把

template<class T>
class auto_ptr{
public:
	explicit auto_ptr(T* p = 0): pointee(p)	{}
	template<class U>
	auto_ptr(auto_ptr<U>& rhs):pointee(rhs.release()) {}//.release作用为智能指针释放对象,并返回原始指针内容
	~auto_ptr(){delete pointee;}
	
	template<class U>
	auto_ptr<T>& operator=(auto_ptr<U>& rhs){
		if(this != &rhs) reset(rhs.release());
		return *this;
	}
	
	T& operator*(){return *pointee;}	//这个函数是可以写成const函数
	T* operator&(){return pointee;}
	T* get(){return pointee};

private:
	T *pointee;
};

很显然,这个auto_ptr是对T* pointee这个指针进行的深层次封装,并为它增添了如下几个接口,除了一个类应有的构造函数,拷贝函数,析构函数之外,他还对指针的行为进行了模仿,例如*ptr和ptr->这一类操作都有相应的模仿。

而我们的迭代器也可以认为是对指针进行了类似的封装,他实现了指针的一些基本功能,同时还实现了对容器相关限制的适应。例如对于链表,我们迭代器的++就应该适应链表的指针移动。

看看链表容器的实现

//链表容器应该有这些东西:插入的方法,输出的方法,链表头尾节点的实现,链表的当前长度
template<typename T>
class List{
	void insert_front(T value);
	void insert_end(T value);
	void display(std::ostream &os = std::cout) const;
private:
	void ListItem<T>* _end;
	void ListItem<T>* _front;
	long _size;
};

//链表的每个元素应该有着自己的值,下一个点的指针相关内容

template<typename T>
class ListItem{
public:
	T value() const {return _value; }
	ListItem* next() const {return _next;}
private:
	T _value;
	ListItem* _next;
};

很显然,当ListItem不暴露在外的话,外部算法很难对List容器内部元素进行处理,于是我们想要设计一个类,他对指针进行封装,指针的指向是ListItem元素。
下文是作者给出的一个ListIter的实现

template <class Item>
struct ListIter{
	Item* ptr;
	ListIter(Item* p = 0):ptr(p){}
	Item& operator*() const {return *ptr;}
	Item* operator->() const {return ptr;}
	ListIter& operator++(){
		ptr = ptr->next();//迭代器自增是对自己封装指针的自增,最终返回的还是自己
		return *this;
	}
	
	ListIter operator++(int){	//注意左值右值的区别
		ListIter temp = *this;
		ptr = ptr->next();
		return temp;
	}
	bool operator==(const ListIter& x){
		return ptr == x.ptr;
	}//迭代器的相等就是里面封装的指针的相等
	
	bool operator!=(const ListIter& x){
		return ptr != x.ptr;
	}
};

可以看到ListIter这个函数对ListItem进行封装的时候暴露了ListItem的一些内容,既然如此,不如直接将迭代器交给容器,让他全权负责。

三.迭代器相应型别

valuetype的使用技巧

迭代器的指向是容器的item,而我们在使用算法时常常会需要知道容器item的值的类型,例如vector,而我们的迭代器需要反映出这个int,而我们通过迭代器是无法直接得出这个型别的,但模板参数推断可以帮助我们办到这个事情

template <class I, class T>
void func_impl(I iter, T t){
	T temp;
};

template <class I>
inline 
void func(I iter){
	func_impl(iter, *iter);
		//*iter 返回 iter指向类型的引用,通过参数推断,我们可以得到它的类型
}

当我们需要迭代器的valuetype在函数体内进行使用时,使用上述技巧(一个泛型负责传入迭代器,里面直接调用另一个函数,另一个函数是function template,利用这个分别传入迭代器和迭代器指向内容可以进行类型推断)可以实现获取valuetype。然而我们想要让valuetype作为函数返回值时上述技巧就失效,因为模板推断是对函数参数进行推断,而函数参数作用在函数体内部,无法作用于返回值处,所以导致我们需要另找方法。

如果我们在迭代器内部就直接将valuetype进行typedef,那么我们就能进行进一步的处理。

template <class T>
struct MyIter{
	typedef T value_type;//通过这一步相当于把模板参数推导的T给永久固定在类中,我们可以直接取它了。娶她,嘿嘿嘿
	T* ptr;
	MyIter(T* p = 0): ptr(p){}
	T& operator*(){return *ptr;}
};

template <class I>
typename I::value_type
func(I iter){
	~~~~;
}

可以看到我们在类内进行typedef能将推导的结果进行保存,并且能够取出使用。
特别的,当我们的T为原生指针的时候,我们是无法进行内嵌型别的定义的,因为原生指针没有被包装,不是class也不是struct,他里面也没有构造他的valuetype。对于这种情况,我们使用偏特化的方法来进行处理

template <typename T>
class C<T*>{///};

有点难以表述上面的内容,总之我们推断的就不是传入类型,而是传入类型为指针的情况下指针所指向的类型。通过这个思想,我们可以设计一个萃取机来萃取迭代器指向的内容,并且能够适配原生指针的情况。

//对于一般情况
template<class T>
class iterator_traits{
	typedef typename T::value_type value_type;
};
//对于一般情况
template<class T>
class iterator_traits<T*>{
	typedef T value_type;
};//对于原生指针的情况

template<class T>
class iterator_traits<const T*>{
	typedef T value_type;
};//对于原生指针的情况

//而我们在使用的使用可以直接这么用
//iterator_traits<iterator>::value_type来直接萃取出迭代器所指向的类型

非常舒适是吧,截至到现在,我们通过对内嵌类型,偏特化,参数推断等c++特性的使用,已经能够从迭代器中获取出他的value_type,无论这个迭代器是一个原生指针还是一个包装好的指针。
我们想要从一个迭代器中萃取出的内容不仅仅是valuetype一种,还有许多我们需要的,例如表示距离的属性difference_type,迭代器的指针内容,迭代器所指的左值引用,还有一个更牛逼的catogory。我们这节简单讲下difference_type, reference_type, pointer type的相关偏特化实现,因为一般实现其实很随意。

#include <cstddef>
template <class T>
class iterator_traits<T*>{
	typedef ptrdiff_t difference_type;
};

template <class T>
class iterator_traits<const T*>{
	typedef ptrdiff_t difference_type;
};
//以上是difference_type的偏特化实现

template <class T>
class iterator_traits<T*>{
	typedef T* pointer;
	typedef T& reference; 
};

template <class T>
class iterator_traits<const T*>{
	typedef const T* pointer;
	typedef const T& reference;
};

偏特化是在是太厉害了!
看看同样手法离谱的iterator_category吧~~!

迭代器相应型别之iterator_category

迭代器的分类

根据移动特性和施行操作,迭代器被分为五类:
input_iterator:所指对象只读
Output_iterator:所致对象唯写
Forward_iterator:单向移动:单链表
Bidirectional iterator:双向移动:双向链表
Random Access Iterator:随机访问:数组
一个算法应该要使用合适的迭代器才能够使效率最大化。
书中给出了这样的一个例子

template <class InputIterator, class Distance>
void advance_II(InputIterator& i, Distance n){
	while(n--)	++i;
	return;
}

template <class BidirectionalIterator, class Distance>
void advance_BI(BiderectionalIterator& i, Distance n){
	if(n > 0) while(n--) ++i;
	if(n < 0) while(n++) --i;
	return ;
}

template <class RandomAccessIterator, class Distance>
void advance_RAI(RandomAccessIterator& i, Distance n){
	i += n;
}

选择不适合的iterator会对效率有很大的影响,例如把一个RAI给一个II算法,那么复杂度直接从O(1)飙升至O(n),那么我们要如何让我们的算法察觉到这个指针的category呢,一个想法一定会从你的脑子里闪过——iterator_traits!!我们也想用一些手法将一个迭代器的category给萃取出来!
书中给了一个不成熟的想法

template <class InputIterator, class Distance>
void distanc(InputIterator& i, Distance n){
	if(check(!!)){
	!!
	}else if(check(!!)){
		!!
	}else {
	}
}

通过编写函数专门对迭代器的型别进行判断是一种方法,但是这对效率有一定的影响,因为我们想要再编译阶段就完成对迭代器的型别进行判断,STL是一个大量使用的,这种对效率的浪费最终会量变引起质变。看看STL是如何做到的吧

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{};

template <class InputIterator, class Distance>
inline void __advance(InputIterator* i, Distance n, input_iterator_tag){。。。。}

template <class ForwardIterator, class Distance>
inline void __advance(ForwardIterator& i, Distance n, Forward_iterator_tag){};
//其他几类迭代器对应的函数大致也是如此
//所以我们在调用的时候大概是按照这样一个逻辑
template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n){
	__advance(i, n, iterator_traits<InputIterator>::iterator_category());
}
//因为xxxx_tag是一个struct,所以后面加个()就相当于给出了一个实例,这样就能激活重载了捏
  • 11
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值