STL源码剖析阅读笔记二(迭代器)

前言

  迭代器的思想在于:将数据容器和算法分开,彼此独立设计,最后再将二者结合。容器和算法的泛型化,从技术角度并不困难。如何将两者有效结合才是重中之重。
  事实上,迭代器我们可以把它理解为一种智能指针。既可以取出内容,也可以像指针一样进行偏移。但是针对不同的容器,我们需要对操作符进行必要的重载,来实现定制化的内容提取和成员取用。

Traits编程技法 - STL源码门钥

1 value_type萃取

  书本前面简单实现了一个list的迭代器,同时引出了一个问题:当我们需要使用迭代器所指的类型去声明一个变量或者判断迭代器类型和本身类型是否相等时该怎么办?这个问题意味着我们的迭代器不仅可以读写数据同时还要能取得对应的类型——value_type。
  此处提出了一个叫做Traits(萃取)编程法——我理解为在内部取出其类别特点再返回的方式。例如:

// 判断迭代器与原类型是否相等
template <typename T>
bool operator!=(const ListItem<T>& item, T n) {
	return item.value() != n;
}

  迭代器的类型是 ListItem 而容器装的数据类型是 T ,使用 != 判断就必须重载该操作符,但是每次传入两个类型显得非常的冗余。因此后续书本对此进行了优化:

#include <iostream>
// 方法1
template <class N, class M>
void func_impl(N ite, M m) {
  M tmp;
}

template <class N>
inline void func1(N iter) {
  func_impl(iter, *iter);
}


// 方法2, Traits
template<class T>
struct Myiter {
  typedef T value_type;
  T *ptr;

  Myiter(T *p = 0) : ptr(p) {

  }

  T &operator*() const { return *ptr; }
};

template<class I>
typename I::value_type func(I ite) { return *ite; }

int main() {

  Myiter<int> ite(new int(3));
  std::cout << "Hello, World!" << std::endl;
  std::cout << *ite << std::endl;
  std::cout << func(ite) << std::endl;
  return 0;
}

  这个优化大大提高了可用性,我们平时使用的find()、erase()都只需要传入迭代器本身即可。
  事实上,上述的做法也不能保证所有类型都适用(当给Myiter<int*>就只能识别为地址类型),只是保证了class type——类的可用性。当遇到原生指针时上面的方法就不适用了。那么必须要有一种迭代器也针对原生指针作特殊处理。

2 Partial Specialization偏特化

  此处提到了当存在多个template参数,我们就必须提供对其中任一参数的特殊处理能力。也就是上面提到的,对原生指针我们也必须能够特殊处理。

template <typename U, typename V, typename T>
class C { ... };

  以上代码为例,偏特化并不是针对上述U、V、T中的任一参数或者参数组合指定具体的参数值。而是针对任何template参数更近一步进行条件限制,设计出另一个特化的版本。

// 任一类的版本
template <typename T>
class C { ... };

// T类型的指针版本
template <typename T>
class C<T*> { ... };

  如何将以上两个进行结合呢?做法是,将value_type再做一层必要的封装。

template <class C>
struct iterator_traits {
  typedef typename C::value_type value_type;
};
template <class D>
typename iterator_traits<D>::value_type func2(D ite) { 
	return *ite; 
}

  上面我们将类型C的value_type做了一层封装。这样做似乎只是加了一层间接转换,但事实不止如此。这样可以让我们的traits拥有一个偏特化的版本。

template <class C>
struct iterator_traits<C*> {
  typedef C value_type;
};

  于是,虽然int*不是一种value_type,但是也可以通过traits取下value type。但是仍然有一个问题:针对指向常数的指针——pointer-to-const,上面的方法返回的仍是常数类型。这样在我们使用时并不方便,因此这里对返回的内容做了处理。

template <class C>
struct iterator_traits<const C*> {
  typedef C value_type;
};

  这样取出来的是C类型,而不是再试const C。

  下图说明了traits所扮演的角色就是萃取出各个迭代器的特性。这里所谓的迭代器特性,指的是迭代器的相应类别。如果想要traits能够有效运作,每一个迭代器都必须遵守这个约定,自行以内嵌型别来定义。
在这里插入图片描述

3 迭代器的五种型别

3.1 value type

  上文提到的value type是其中之一。这里再赘述。

3.2 difference type

  difference type用来表示两个迭代器之间的距离,也因此,它可以用来表示一个容器的最大容量。

template <class I, class T>
typename iterator_traits<I>::defference_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;
}

  针对指针以及类做了不同的偏特化版本:

// 普通类版本
template <class I>
struct iterator_traits {
	...
	typedef typename I::difference_type difference_type;
};

// 指针版本
template <class I>
struct iterator_traits<I*> {
	...
	typedef typename I::difference_type difference_type;
};

// 常量指针版本
template <class I>
struct iterator_traits<const I*> {
	...
	typedef typename I::difference_type difference_type;
};

3.3 reference type

  迭代器指向的内容分两种:可以修改的为mutable iterators。
不可修改的为:constant iterators。当我们对一个mutable iterators做提领动作时,获得的应该是左值,而不是右值。因为右值不允许做赋值操作,只有左值可以。
  当取左值时,返回的应该是T&而不是T。

3.4 pointer type

  pointers和references在c++中有非常密切的关联。如果传回一个左值,并且代表了所指之物是可能的。那么传回一个左值,令他代表所指之物的地址也是可能的。

  pointer type指针可以代表指向的内容的地址。

3.5 iterator_category

  最后一种是讲说迭代器的类别。这个类别指的是迭代器本身的类别。根据特性可以分为5类:
  1.input iterator 只读类别
  2.output iterator 只写类别
  3.forward iterator 允许写入或读取到此迭代器所形成区域
  4.bidirectional iterator 双向迭代
  5.random access iterator 支持所有指针操作(前三种只支持operator++,第四种加上了operator–)。
在这里插入图片描述
  当存在多个版本的迭代器需求时,我们需要对各个版本作对应的多态操作:

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)
		advance_BI(i,n);
	else
		advance_II(i,n);
}

  但是在执行期才决定使用哪个版本,会影响程序的效率。因此最好能在编译其就选择对应的版本。

多载化:
  traits可以取出迭代器的种类,因此我们可以利用迭代器类型作为第三参数。下面定义五个类,代表五种迭代器类型:

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函数可以做以下调整:

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

// 这是一个单纯的转调用函数,后面会讨论如何免除。
template <class ForwardIterator, class Distance>
inline void _advance(ForwardIterator &i, Distance n, forward_iterator_tag) {
	// 单纯地进行转调用
	advance(i, n, input_iterator_tag)
}

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 RandomAccessIterator, class Distance>
inline void _advance(RandomAccessIterator &i, Distance n, random_access_iterator_tag) {
	// 双向跳跃
	i += n;
}

  按照上面的做法将会产生一些临时变量,造成不必要的浪费。而且使用独自类型作为传入参数的做法有些不是很理想。因此,我们可以考虑消除单纯的转调用。
  这个方法就是使用继承来实现迭代器的多态。
在这里插入图片描述

总结

  整个迭代器章节我们最大的收获是traits技法。其实traits技法我理解为一种策略的传递,通过不同的类型我们使用了不同的策略进行处理。traits技法是c++重要的一种技巧,可以有效提高程序的效率。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值