STL源码--iterator和traits编程技法

时间:2022-06-01 21:25:32

第一部分 iterator学习

STL iterators定义:

提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表达方式。

任何iteartor都应该提供5个内嵌相应型别:

1. value_type;

2. difference_type;

3. pointer;

4. reference;

5. iterator_category;

下面分别介绍5个内嵌型别:

1. value_type:

由于是泛型编程,因此容器中的数据类型并不能确定,如一个函数传进去一个参数是迭代器类型,但是在内部真正操作时却是对应的实际类型,尤其是函数的返回值更无法声明。。。因此,需要有一套机制可以让编译器知道某个iterator在传进函数时,代表的是什么类型,这样函数才可能定义。基于此思想,就有了value_type的概念,value_type代表iterator所指向的真实数据的类型。

在结构体的定义中,加入value_type,内嵌声明这个迭代器指向的类T的类型,如下所示:

template <class T>
struct MyIter // 里面加入一个类型定义value_type
{
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) // 通过I::value_type返回I的类型,注意typename不能省略
{
return *ite;
}

...
MyIter<int> ite(new int(8));
Func(ite); // 可以直接运行,通过MyIter::value_type来得到返回值的类型

这段代码就是一个声明内嵌型别的例子,可以通过typename I::value_type得到迭代器指向的内容的类型。这就像是一个萃取工艺,通过把迭代器放进来就可以得到他指向的数据的类型,我们叫这种技术为traits技法,STL源码这样使用:

// 定义萃取迭代器类型的方法:
template <class T>
struct iterator_traits
{
typedef typename I::value_type value_type;
};

// 前述的函数定义就改为:
typename iterator_traits<I>::value_type func(I ite);

但是还有一个问题就是通过这段代码不能得到原生指针(如int*)对应的类型,需要使用到C++的偏特化语法来实现:

// 对于原生指针(如int*),用下面的偏特化模型
template <class T>
struct iterator_traits<T*>
{
typedef T value_type;
};

// 对于常量原生指针(如const int*),用下面的偏特化模型
template <class T>
struct iterator_traits<const T*>
{
typedef T value_type;
};

至此关于value_type的介绍就差不多了,由于其他几种内嵌型别在代码实现上是类似的,下面就简单做下介绍

2. difference_type:

用来表示两个迭代器之间的距离,也可以用来计算容器的容量。

3. pointer:

用来返回指向内容的指针。

3. reference:

用来返回指向对象的引用。

4. pointer:

用来返回指向内容的指针。

5. iterator_category:

用来返回迭代器的种类。iterator分为5个大类:input iterator(read only), ouput iterator(write only), forward iterator(支持iter++), bidirectional iterator(支持iter++, iter--), random access iterator(除前述功能外,还支持iter + n,iter - n, iter[n]等等). 大致关系如下:

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

为了一些算法能得到比较高的效率,有些时候需要知道迭代器的类型,比如找iter[n]的方法,随机访问的迭代器是最快的O(1),其他迭代器是O(n),因此对于找iter[n]的方法,需要先判断迭代器的类型,然后再调用函数,会更加有效率。

总结一下,iterator的具有一些内嵌的型别声明,这些声明在STL的泛型编程中非常有用,下面就是STL源码中的一些定义,也是提供给iterator建立者的一个模板,以免作者丢掉5个内嵌声明之一,用户在定义时,只需要从下面的模板继承即可,因为模板中只有内嵌声明,因此不会额外的增加内存等开销,方便使用。

STL提供了一个iterator class的模板,自行开发的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;
};

// 五种iterator类型
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{};

// 萃取器traits
template <class Iterator>
struct iterator_traits
{

  typedef typename Iterator::iterator_category iterator_category;

  typedef typename Iterator::value_type      value_type;

  typedef typename Iterator::difference_type   difference_type;

  typedef typename Iterator::pointer        pointer;

  typedef typename Iterator::reference        reference;
};

// 原生指针的偏特化版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;
};

第二部分 traits编程技法:

第一部分提到了iterator的traits技法,从根本上讲,就是如何通过一种技术让编译器知道迭代器指向的数据是什么类型,那是不是可以通过traits技法得到一个对象或者一个类的类型或其他的信息呢?答案是肯定的。从iterator种引出了traits的概念,因此可以将这个概念扩大化来得到型别的特征,例如,有些对象是很单纯的对象不需要特别的用构造函数或析构函数调用或copy,那最有效率的方法就是直接通过内存的覆盖或内存移动,不需要通过构造函数或析构函数来操作,可以大大的提高运行效率。因此,有了下面的“类型萃取”方法(__type_traits):

template <class type>
struct __type_traits
{
typedef __true_type this_dummy_member_must_be_first; // 占位用,以防有些编译器有自己的__type_traits
typedef __false_type has_trivial_default_constructor; // 默认的都是__false_type,是最保守的定义方式,说明都是需要用构造函数等操作的
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;
};

__type_traits通过内嵌声明,可以得到一个类是否具有平凡的默认构造函数,是否有平凡的copy构造函数,是否有平凡的赋值构造函数,是否有平凡的析构函数和是否标量型。知道了这些信息以后,就可以通过更加高效的手段来实现construct, copy, assign, destruct和初始化了。