STL源码剖析——iterators与trait编程#2 Traits编程技法

Posted misakijh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL源码剖析——iterators与trait编程#2 Traits编程技法相关的知识,希望对你有一定的参考价值。

  在算法中运用迭代器时,很可能用到其相应类型。什么是相应类型?迭代器所指对象的类型便是其中一个。我曾有一个错误的理解,那就是认为相应类型就是迭代器所指对象的类型,其实不然,相应类型是一个大的类别,迭代器所指对象的类型只是里面的其中一个。后面会讨论到相应类型的另外几种。

  假设算法需要声明一个变量,以“迭代器所指对象的类型”为类型,该怎么做?或许我们可能会想到RTTI性质中的typeid(),但获得的只是类型名称,并不能拿来声明变量。

  其中一个解决方法是:利用模版函数中的参数推导(argument deducation)机制:

 1 template <typename I, typename T>
 2 void func_impl(I iter, T t)
 3 {
 4     T tmp;
 5     //...做原本func()应该做的全部工作
 6 }
 7 
 8 template<typename I>
 9 inline void func(I iter)
10 {
11     func_impl(iter, *iter);
12 }

  我们以func()为对外接口,却把实际操作全部置于func_impl()之中。由于func_impl()是一个模版函数,一旦被调用,编译器会自动进行参数推导。于是导出类型T,解决了问题。

  然而上面也有提到,迭代器的相应类型不只是迭代器所指对象的类型一种而已。根据经验,最常用的相应类型有5种,然而并非任何情况下任何一种都可以利用上述的参数推导机制来取得。我们需要更全面的解法。

Traits编程技法

  迭代器所指对象的类型,称为该迭代器的value type。上述的参数类型推导虽然可以用于value type,但并不是什么情况都能用,例如需要把value type当作函数的返回值,这个办法就行不通了,毕竟模版函数的参数推导机制只能推导参数,无法推导函数的返回值类型。

  我们需要其他方法,为类型T定义一个别名似乎是一个好主意,例如:

 1 template<typename I>
 2 inline void func(I iter)
 3 {
 4     func_impl(iter, *iter);
 5 }
 6 
 7 template<typename T>
 8 struct MyIter
 9 {
10     typedef T value_type;
11     T* ptr;
12     MyIter(T* p = 0) :ptr(p){}
13     T& operator*()const { return *ptr; }
14     //...
15 };
16 
17 template<typename T>
18 typename I::value_type func(I ite)
19 {
20     return *ite;
21 }
22 
23 MyIter<int> ite(new int(8));
24 cout << func(ite);    //输出 8

  T是一个模板参数,在它被编译器具现化之前,编译器对T一无所知,换句话说,编译器此时并不知道MyIter<T>::value_type代表的是一个类型或是一个成员函数或是一个数据成员变量。关键词typename的用意在于告诉编译器这是一个类型,如此才能顺利通过编译。

  这似乎可行,但有一点需要注意,并不是所有的迭代器都是类的对象,例如原生指针,如果不是类对象,就无法为它定义别名。但STL绝对必须接受原生指针作为一种迭代器,所以上面这样还不够。那么有没有办法可以让上述的一般化操作针对特定情况做特殊化处理呢?

  模版偏特化可以做到,如果模板类拥有一个以上的模版参数,我们可以针对其中的某个或数个模版参数进行特化。换句话说,我们可以在泛化设计中提供一个特化版本,指明某些模板参数。假设有一个模版类如下:

1 template<typename T>
2 class C
3 {...};

  偏特化的字面意思容易误导我们,让我们以为所谓的偏特化版本是对参数V指定某个参数值。其实不然,所谓的partial specialization的意思是提供另一份模板定义式,而其本身仍为模版。由此,面对上述这个模板类,我们可以提供另外一份定义:

1 template<typename T>
2 class C<T*>        //这个特化版本仅适用于T为原生指针的情况
3 {...};

  至此,对于value_type而言,我们有了比较好的提取类型的解决方法。那么Traits编程也已呼之欲出了,何为traits,我的理解就是通过一定的手段把某个类的特征提取出来,在侯捷老师的书中就叫萃取类的特性,何为类的特征或特性?这里可以分为两个类别去阐述,一是迭代器类,二是普通类,两个类别对应的特征不同,本节主要讲迭代器类的特征(也就是前面所述的相应类型,value_type就是其中一种),之后的章节再讲普通类的特征及其提取。而迭代器的相应类型最常用到的有五种,分别是:value type, difference type, pointer, reference, iterator catagoly。

迭代器相应类型之一:value type

  所谓value type,是指迭代器所指对象的类型。任何一个打算与STL算法有完美搭配的类,都应该定义自己的value type,做法如上所述。

迭代器相应类型之二:difference type

  用来表示两个迭代器之间的距离,因此可以用它来表示一个容器的最大容量,因为对于连续空间的容器而言,头尾之间的距离就是其最大容量。其实我有点不太理解,为什么要为两个迭代器之间的距离刻意定义一个别名,例如STL的count(),其传回值必须使用迭代器的difference type:

 1 template<typename I, typename T>
 2 typename itertor_traits<I>::difference_type    //函数返回值,是个类型
 3 count(I first, I last, const T& value) {
 4     typename itertor_traits<I>::difference_type n = 0;
 5     for ( ; first != last; ++first)
 6     {
 7         if (*first == value)
 8             ++n;
 9     }
10     return n;
11 }

  实际上定义一个整型变量来记录也是可以的,所以我不太明白difference type的用意。

  于原生指针而言,也有类似意义的类型ptrdiff_t(定义于<cstddef>头文件),所以在特化版本中,就把ptrdiff_t作为原生指针的difference_ type:

 1 template<typename I>
 2 struct iterator_traits
 3 {
 4     ...
 5     typedef typename I::difference_type  difference_type;
 6 };
 7 //针对原生指针而设计的偏特化版本
 8 template<typename T>
 9 struct iterator_traits<T*>
10 {
11     ...
12     typedef ptrdiff_t difference_type;
13 };
14 //针对原生的pointer-to-const而设计的偏特化版本
15 struct iterator_traits<const T*>
16 {
17     ...
18     typedef ptrdiff_t difference_type;
19 };

迭代器相应类型之三:reference type

  我的理解是,reference type就是表示该类型变量的引用表示,例如要是想返回某迭代器所指类型变量的引用(需要用作左值时),用reference type就好了。侯捷书中并未对reference type作过多的解释,而是把注意力放在了T&和const T&上,即constant iterators(不允许改变所指对象的内容)和mutable iterators(允许改变所指对象的内容)。当p是个mutable iterator时,其value type是T,那么reference type就应该是T&;如果p是一个constant iterators,其value type是T,那么reference type就应该是const T&。实现的细节将在下节给出iterator源码时讨论。

迭代器相应类型之四:pointer type

  我的理解是,pointer type就是表示该类型变量的地址表示,也就是说,我们能够传回一个pointer,指向迭代器所指之物。这些相应类型已在先前的ListIter class中出现过:

1 Item& operator*()const { return *ptr; }
2 Item* operator->()const { return ptr; }

  Item&便是ListIter的reference type,而Item* 便是其pointer type。现在我们可以把reference type和pointer type这两个相应类型加入traits内:

 1 template<typename I>
 2 struct iterator_traits
 3 {
 4     ...
 5     typedef typename I::pointer pointer;
 6     typedef typename I::reference reference;
 7 };
 8 template<typename T>
 9 struct iterator_traits<T*>
10 {
11     ...
12     typedef T* pointer;
13     typedef T& reference;
14 };
15 struct iterator_traits<const T*>
16 {
17     ...
18     typedef const T* pointer;
19     typedef const T& reference;
20 };

   而由于迭代器相应类型之五占据的篇幅过多,而本节讲的东西已经有点多了,所以把它放在下节再进行学习。

以上是关于STL源码剖析——iterators与trait编程#2 Traits编程技法的主要内容,如果未能解决你的问题,请参考以下文章

STL源码剖析——iterators与trait编程#2 Traits编程技法

STL源码剖析——iterators与trait编程#3 iterator_category

STL源码剖析——iterators与trait编程#1 尝试设计一个迭代器

stl源码剖析学习笔记traits编程技法简明例程

STL源码剖析(迭代器)

《STL源码剖析》学习之traits编程